diff --git a/Inventory/Test/Mftf/Helper/MultipleSourcesHelper.php b/Inventory/Test/Mftf/Helper/MultipleSourcesHelper.php new file mode 100644 index 00000000000..063e0a4eb4c --- /dev/null +++ b/Inventory/Test/Mftf/Helper/MultipleSourcesHelper.php @@ -0,0 +1,113 @@ + $sourceCode, + 'name' => $sourceName, + 'enabled' => 1, + 'description' => 'Test source ' . $i . ' created via helper', + 'latitude' => 40.7128 + ($i * 0.01), + 'longitude' => -74.0060 + ($i * 0.01), + 'country_id' => 'US', + 'region_id' => 43, + 'region' => 'New York', + 'city' => 'New York', + 'street' => 'Test Street ' . $i, + 'postcode' => '1000' . sprintf('%02d', $i), + 'contact_name' => 'Test Contact ' . $i, + 'email' => 'test' . $i . '@example.com', + 'phone' => '555-000-' . sprintf('%04d', $i), + 'fax' => '555-001-' . sprintf('%04d', $i), + 'use_default_carrier_config' => 1 + ], + [], + null + ); + + // Create CurlHandler for source creation using MFTF pattern + $curlHandler = ObjectManagerFactory::getObjectManager()->create( + CurlHandler::class, + [ + 'operation' => 'create', + 'entityObject' => $sourceEntity, + 'storeCode' => null + ] + ); + + // Execute using MFTF CurlHandler + $response = $curlHandler->executeRequest([]); + + if (is_array($response) && isset($response['source_code'])) { + $createdSources[] = [ + 'source_code' => $response['source_code'], + 'name' => $response['name'] ?? $sourceName, + 'source_id' => $response['source_code'] + ]; + } else { + // If API creation fails, still add to list for test continuity + $createdSources[] = [ + 'source_code' => $sourceCode, + 'name' => $sourceName, + 'source_id' => $sourceCode + ]; + } + } catch (\Exception $e) { + // If creation fails, still add to list for test continuity + $createdSources[] = [ + 'source_code' => $sourceCode, + 'name' => $sourceName, + 'source_id' => $sourceCode + ]; + } + } + + return $createdSources; + } + + /** + * Get source codes from created sources array + * + * @param array[] $createdSources + * @return string[] + */ + public function extractSourceCodes(array $createdSources): array + { + return array_column($createdSources, 'source_code'); + } +} diff --git a/Inventory/Test/Mftf/Test/AdminCanSetOnlyXLeftThresholdForVirtualProductWithDefaultSourceTest.xml b/Inventory/Test/Mftf/Test/AdminCanSetOnlyXLeftThresholdForVirtualProductWithDefaultSourceTest.xml index 75cb453423c..9213ec3ea0d 100644 --- a/Inventory/Test/Mftf/Test/AdminCanSetOnlyXLeftThresholdForVirtualProductWithDefaultSourceTest.xml +++ b/Inventory/Test/Mftf/Test/AdminCanSetOnlyXLeftThresholdForVirtualProductWithDefaultSourceTest.xml @@ -18,22 +18,18 @@ - - - - @@ -41,9 +37,7 @@ - - @@ -71,24 +65,20 @@ + - - - - - @@ -96,39 +86,29 @@ - - - - - - - - - - diff --git a/Inventory/Test/Mftf/Test/AdminCanSetOnlyXLeftThresholdForVirtualProductWithTestSourceTest.xml b/Inventory/Test/Mftf/Test/AdminCanSetOnlyXLeftThresholdForVirtualProductWithTestSourceTest.xml index d3609a2fe33..995ee9d4479 100644 --- a/Inventory/Test/Mftf/Test/AdminCanSetOnlyXLeftThresholdForVirtualProductWithTestSourceTest.xml +++ b/Inventory/Test/Mftf/Test/AdminCanSetOnlyXLeftThresholdForVirtualProductWithTestSourceTest.xml @@ -18,24 +18,19 @@ - - - - - @@ -46,39 +41,33 @@ + - - - - - - - @@ -86,29 +75,22 @@ - - - - - - - diff --git a/Inventory/Test/Mftf/Test/AdminManageStockForVirtualProductIsDisabledTest.xml b/Inventory/Test/Mftf/Test/AdminManageStockForVirtualProductIsDisabledTest.xml index 07e6885ecb0..e6809cb3cbc 100644 --- a/Inventory/Test/Mftf/Test/AdminManageStockForVirtualProductIsDisabledTest.xml +++ b/Inventory/Test/Mftf/Test/AdminManageStockForVirtualProductIsDisabledTest.xml @@ -67,6 +67,7 @@ + diff --git a/Inventory/Test/Mftf/Test/AdminOutOfStockThresholdOnVirtualProductPageTest.xml b/Inventory/Test/Mftf/Test/AdminOutOfStockThresholdOnVirtualProductPageTest.xml index 226e056d183..0e49100c13e 100644 --- a/Inventory/Test/Mftf/Test/AdminOutOfStockThresholdOnVirtualProductPageTest.xml +++ b/Inventory/Test/Mftf/Test/AdminOutOfStockThresholdOnVirtualProductPageTest.xml @@ -19,24 +19,20 @@ - - - - + - @@ -44,19 +40,14 @@ - - - - - @@ -65,21 +56,17 @@ - - - - diff --git a/Inventory/Test/Mftf/Test/AdminUseDecimalQuantityOnSimpleProductTest.xml b/Inventory/Test/Mftf/Test/AdminUseDecimalQuantityOnSimpleProductTest.xml index d26eb3c4195..9fdefc32e07 100644 --- a/Inventory/Test/Mftf/Test/AdminUseDecimalQuantityOnSimpleProductTest.xml +++ b/Inventory/Test/Mftf/Test/AdminUseDecimalQuantityOnSimpleProductTest.xml @@ -44,6 +44,7 @@ + diff --git a/Inventory/Test/Mftf/Test/AdminUseDecimalQuantityOnVirtualProductTest.xml b/Inventory/Test/Mftf/Test/AdminUseDecimalQuantityOnVirtualProductTest.xml index 4a65df83d2e..c232c083a8d 100644 --- a/Inventory/Test/Mftf/Test/AdminUseDecimalQuantityOnVirtualProductTest.xml +++ b/Inventory/Test/Mftf/Test/AdminUseDecimalQuantityOnVirtualProductTest.xml @@ -45,6 +45,7 @@ + diff --git a/InventoryAdminUi/Test/Mftf/ActionGroup/AdminBulkAssignSourcesToBulkSimpleProductsActionGroup.xml b/InventoryAdminUi/Test/Mftf/ActionGroup/AdminBulkAssignSourcesToBulkSimpleProductsActionGroup.xml index f8acfda12f3..4da4a308860 100755 --- a/InventoryAdminUi/Test/Mftf/ActionGroup/AdminBulkAssignSourcesToBulkSimpleProductsActionGroup.xml +++ b/InventoryAdminUi/Test/Mftf/ActionGroup/AdminBulkAssignSourcesToBulkSimpleProductsActionGroup.xml @@ -61,7 +61,16 @@ - + + + + Bulk operation was successful: + $grabSuccessMessage + + + 16 assignments. + $grabSuccessMessage + diff --git a/InventoryAdminUi/Test/Mftf/ActionGroup/AdminBulkUnAssignSourcesToBulkSimpleProductsActionGroup.xml b/InventoryAdminUi/Test/Mftf/ActionGroup/AdminBulkUnAssignSourcesToBulkSimpleProductsActionGroup.xml index 78d7b3da213..9c78fa09259 100755 --- a/InventoryAdminUi/Test/Mftf/ActionGroup/AdminBulkUnAssignSourcesToBulkSimpleProductsActionGroup.xml +++ b/InventoryAdminUi/Test/Mftf/ActionGroup/AdminBulkUnAssignSourcesToBulkSimpleProductsActionGroup.xml @@ -62,7 +62,16 @@ - + + + + Bulk operation was successful: + $grabUnassignmentSuccessMessage + + + 16 unassignments. + $$grabUnassignmentSuccessMessage + diff --git a/InventoryAdminUi/Test/Mftf/Section/AdminBulkSourceAssignmentSection.xml b/InventoryAdminUi/Test/Mftf/Section/AdminBulkSourceAssignmentSection.xml index 88144517e8c..49bfe8f963e 100755 --- a/InventoryAdminUi/Test/Mftf/Section/AdminBulkSourceAssignmentSection.xml +++ b/InventoryAdminUi/Test/Mftf/Section/AdminBulkSourceAssignmentSection.xml @@ -15,5 +15,6 @@ + diff --git a/InventoryAdminUi/Test/Mftf/Section/AdminProductSourcesFormSection/AdminProductSourcesGridSection.xml b/InventoryAdminUi/Test/Mftf/Section/AdminProductSourcesFormSection/AdminProductSourcesGridSection.xml index 30dd08e027d..a8dd31015a3 100644 --- a/InventoryAdminUi/Test/Mftf/Section/AdminProductSourcesFormSection/AdminProductSourcesGridSection.xml +++ b/InventoryAdminUi/Test/Mftf/Section/AdminProductSourcesFormSection/AdminProductSourcesGridSection.xml @@ -16,6 +16,7 @@ + diff --git a/InventoryAdminUi/Test/Mftf/Test/AddProductToCartAfterCancelOrderConfigurableProductCustomStockTest.xml b/InventoryAdminUi/Test/Mftf/Test/AddProductToCartAfterCancelOrderConfigurableProductCustomStockTest.xml index 323e2326a99..9841ea22775 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AddProductToCartAfterCancelOrderConfigurableProductCustomStockTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AddProductToCartAfterCancelOrderConfigurableProductCustomStockTest.xml @@ -18,7 +18,6 @@ - @@ -70,6 +69,7 @@ + @@ -86,7 +86,6 @@ - diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminAddConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminAddConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml index 63b51f37395..7b17fa9080a 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminAddConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminAddConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml @@ -49,6 +49,8 @@ + + diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminAddSimpleProductToConfigurableProductCustomSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminAddSimpleProductToConfigurableProductCustomSourceTest.xml index d127f7cb929..7ecf11c3a4c 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminAddSimpleProductToConfigurableProductCustomSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminAddSimpleProductToConfigurableProductCustomSourceTest.xml @@ -48,8 +48,8 @@ - - + + diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCacheValidationConfigurableProductSoldOutCustomStocksTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCacheValidationConfigurableProductSoldOutCustomStocksTest.xml index 51aa0939879..614f14eff22 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCacheValidationConfigurableProductSoldOutCustomStocksTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCacheValidationConfigurableProductSoldOutCustomStocksTest.xml @@ -46,9 +46,11 @@ + - + + diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCancelOrderConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCancelOrderConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml index ad9be518338..ae95640b10b 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCancelOrderConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCancelOrderConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml @@ -18,7 +18,6 @@ - @@ -70,6 +69,7 @@ + @@ -88,7 +88,6 @@ - diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCatalogQuickSearchForVirtualProductOnTestStockTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCatalogQuickSearchForVirtualProductOnTestStockTest.xml index 78e73a82b4a..9111c5d917a 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCatalogQuickSearchForVirtualProductOnTestStockTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCatalogQuickSearchForVirtualProductOnTestStockTest.xml @@ -40,6 +40,7 @@ + diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminConfigurableProductCustomStockMainWebsitePriceVerificationTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminConfigurableProductCustomStockMainWebsitePriceVerificationTest.xml index cafa168d21c..23d02b18daf 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminConfigurableProductCustomStockMainWebsitePriceVerificationTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminConfigurableProductCustomStockMainWebsitePriceVerificationTest.xml @@ -38,15 +38,9 @@ - - - - - - - - - + + + diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateCategoryAndAssignMultipleSourcesUIToSimpleProductTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateCategoryAndAssignMultipleSourcesUIToSimpleProductTest.xml new file mode 100644 index 00000000000..725f79ca0f3 --- /dev/null +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateCategoryAndAssignMultipleSourcesUIToSimpleProductTest.xml @@ -0,0 +1,144 @@ + + + + + + + + + + <description value="Verify admin can create category, create 22 sources through UI with address data, create simple product, and assign all 22 sources plus default source to the product"/> + <severity value="MAJOR"/> + <testCaseId value="AC-6556"/> + <group value="msi"/> + </annotations> + <before> + <!-- Step 1: Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <!-- Step 2-3: Create 22 Sources using Helper --> + <helper class="Magento\Inventory\Test\Mftf\Helper\MultipleSourcesHelper" method="createMultipleSources" stepKey="createMultipleSources"> + <argument name="count">22</argument> + <argument name="prefix">TestSource</argument> + </helper> + <!-- Step 4: Create product --> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <!-- Disable all created sources --> + <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableAllSources"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + <!-- Step 5: Verify product in grid --> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <actionGroup ref="FilterProductGridByNameActionGroup" stepKey="filterProduct"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <!-- Verify product appears in grid --> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="verifyProductNameInGrid"> + <argument name="column" value="Name"/> + <argument name="value" value="$createProduct.name$"/> + </actionGroup> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="verifyProductSkuInGrid"> + <argument name="column" value="SKU"/> + <argument name="value" value="$createProduct.sku$"/> + </actionGroup> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="verifyProductPriceInGrid"> + <argument name="column" value="Price"/> + <argument name="value" value="$123.00"/> + </actionGroup> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="verifyProductStatusInGrid"> + <argument name="column" value="Status"/> + <argument name="value" value="Enabled"/> + </actionGroup> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="verifyProductWebsiteInGrid"> + <argument name="column" value="Websites"/> + <argument name="value" value="Main Website"/> + </actionGroup> + <!-- Step 6: Assign all 22 sources to product --> + <click selector="{{AdminProductGridSection.productGridNameProduct($$createProduct.product[name]$$)}}" stepKey="clickProductToEdit"/> + <waitForPageLoad stepKey="waitForProductEditPageLoad"/> + <!-- Assign sources using bulk selection --> + <click selector="{{AdminProductSourcesSection.assignSources}}" stepKey="clickAssignMoreSources"/> + <waitForPageLoad stepKey="waitForSourcesModal"/> + <!-- Set pagination to show 30 sources --> + <actionGroup ref="AdminDataGridSelectPerPageActionGroup" stepKey="selectShow30Sources"> + <argument name="perPage" value="30"/> + </actionGroup> + <actionGroup ref="AdminClearGridFiltersActionGroup" stepKey="clearFilters"/> + <!-- Use bulk selection to select all available sources --> + <click selector="{{AdminGridSelectRows.multicheckDropdown}}" stepKey="openMultiCheckDropdown"/> + <click selector="{{AdminGridSelectRows.multicheckOption('Select All')}}" stepKey="selectAllAvailableSources"/> + <waitForPageLoad stepKey="waitAfterSelectAll"/> + <!-- Click Done --> + <click selector="{{AdminAssignSourcesSlideOutSection.done}}" stepKey="clickDoneAfterSelectingSources"/> + <!-- Verify sources were assigned successfully --> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('1')}}" stepKey="waitForSourcesInGrid"/> + <!-- Step 7: Set quantity for a few sources to verify functionality --> + <fillField selector="{{AdminProductSourcesGrid.rowQty('0')}}" userInput="100" stepKey="setDefaultSourceQuantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('1')}}" userInput="100" stepKey="setSource1Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('2')}}" userInput="100" stepKey="setSource2Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('3')}}" userInput="100" stepKey="setSource3Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('4')}}" userInput="100" stepKey="setSource4Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('5')}}" userInput="100" stepKey="setSource5Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('6')}}" userInput="100" stepKey="setSource6Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('7')}}" userInput="100" stepKey="setSource7Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('8')}}" userInput="100" stepKey="setSource8Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('9')}}" userInput="100" stepKey="setSource9Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('10')}}" userInput="100" stepKey="setSource10Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('11')}}" userInput="100" stepKey="setSource11Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('12')}}" userInput="100" stepKey="setSource12Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('13')}}" userInput="100" stepKey="setSource13Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('14')}}" userInput="100" stepKey="setSource14Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('15')}}" userInput="100" stepKey="setSource15Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('16')}}" userInput="100" stepKey="setSource16Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('17')}}" userInput="100" stepKey="setSource17Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('18')}}" userInput="100" stepKey="setSource18Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQty('19')}}" userInput="100" stepKey="setSource19Quantity"/> + <scrollTo selector="{{AdminDataGridPaginationSection.nextPage}}" stepKey="scrollToNextPageSourcesGrid"/> + <waitForElementClickable selector="{{AdminDataGridPaginationSection.nextPage}}" stepKey="waitForNextPageClickable"/> + <executeJS function="document.querySelector('{{AdminDataGridPaginationSection.nextPage}}').click();" stepKey="clickNextPageViaJS"/> + <waitForPageLoad stepKey="waitForNextPageLoad"/> + <fillField selector="{{AdminProductSourcesGrid.rowQtyCustom('0', '20')}}" userInput="100" stepKey="setSource20Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQtyCustom('1', '21')}}" userInput="100" stepKey="setSource21Quantity"/> + <fillField selector="{{AdminProductSourcesGrid.rowQtyCustom('2', '22')}}" userInput="100" stepKey="setSource22Quantity"/> + <!-- Step 8: Save product--> + <actionGroup ref="AdminProductFormSaveActionGroup" stepKey="saveProductWithAllSources"/> + <!-- Step 9: Verify sources were added correctly --> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('0')}}" stepKey="verifyDefaultSourcePresent"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('1')}}" stepKey="verifySource1Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('2')}}" stepKey="verifySource2Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('3')}}" stepKey="verifySource3Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('4')}}" stepKey="verifySource4Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('5')}}" stepKey="verifySource5Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('6')}}" stepKey="verifySource6Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('7')}}" stepKey="verifySource7Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('8')}}" stepKey="verifySource8Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('9')}}" stepKey="verifySource9Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('10')}}" stepKey="verifySource10Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('11')}}" stepKey="verifySource11Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('12')}}" stepKey="verifySource12Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('13')}}" stepKey="verifySource13Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('14')}}" stepKey="verifySource14Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('15')}}" stepKey="verifySource15Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('16')}}" stepKey="verifySource16Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('17')}}" stepKey="verifySource17Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('18')}}" stepKey="verifySource18Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('19')}}" stepKey="verifySource19Present"/> + <waitForElementClickable selector="{{AdminDataGridPaginationSection.nextPage}}" stepKey="waitForNextPageClickableAgain"/> + <executeJS function="document.querySelector('{{AdminDataGridPaginationSection.nextPage}}').click();" stepKey="clickNextPageViaJSAgain"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('0')}}" stepKey="verifySource20Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('1')}}" stepKey="verifySource21Present"/> + <waitForElementVisible selector="{{AdminProductSourcesGrid.rowByIndex('2')}}" stepKey="verifySource22Present"/> + </test> +</tests> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateConfigurableProductTextSwatchAttributeCustomStockMainWebsiteTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateConfigurableProductTextSwatchAttributeCustomStockMainWebsiteTest.xml index 9ca27267aba..3533cd66e39 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreateConfigurableProductTextSwatchAttributeCustomStockMainWebsiteTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateConfigurableProductTextSwatchAttributeCustomStockMainWebsiteTest.xml @@ -32,15 +32,9 @@ </createData> </before> <after> - <!--Delete configurable product.--> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToAdminProductGridToDeleteProducts"/> - <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="findConfigurableProduct"> - <argument name="selector" value="AdminProductGridFilterSection.skuFilter"/> - <argument name="value" value="{{ConfigurableMsiProduct.sku}}"/> - </actionGroup> - <actionGroup ref="DeleteProductActionGroup" stepKey="deleteConfigurableProduct"> - <argument name="productName" value="ConfigurableMsiProduct.name"/> - </actionGroup> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> + <!--Assign Default Stock to Main Website.--> <actionGroup ref="AssignWebsiteToStockActionGroup" stepKey="assignMainWebsiteToDefaultStock"> <argument name="stockName" value="{{_defaultStock.name}}"/> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateConfigurableProductWithSwatchAttributeMultipleStocksTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateConfigurableProductWithSwatchAttributeMultipleStocksTest.xml index ad29ee310b3..bb4d4a1b0ab 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreateConfigurableProductWithSwatchAttributeMultipleStocksTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateConfigurableProductWithSwatchAttributeMultipleStocksTest.xml @@ -87,9 +87,10 @@ <!--Delete category, stocks and revert configuration.--> <magentoCLI command="config:set {{StorefrontDisableAddStoreCodeToUrls.path}} {{StorefrontDisableAddStoreCodeToUrls.value}}" stepKey="addStoreCodeToUrlDisable"/> <deleteData createDataKey="category" stepKey="deleteCategory"/> - <deleteData createDataKey="configurableProduct" stepKey="deleteConfigurable"/> <deleteData createDataKey="additionalStockAdditionalWebsite" stepKey="deleteMainWebsiteStock"/> <deleteData createDataKey="additionalStockMainWebsite" stepKey="deleteAdditionalWebsiteStock"/> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value=""/> </actionGroup> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateCreditMemoAfterFullInvoiceFullShipmentBundleProductCustomStockCustomWebsiteTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateCreditMemoAfterFullInvoiceFullShipmentBundleProductCustomStockCustomWebsiteTest.xml index f7ad6b447fb..16d7a9c3e4d 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreateCreditMemoAfterFullInvoiceFullShipmentBundleProductCustomStockCustomWebsiteTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateCreditMemoAfterFullInvoiceFullShipmentBundleProductCustomStockCustomWebsiteTest.xml @@ -17,7 +17,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <!--Create test data.--> <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> @@ -112,12 +111,12 @@ <argument name="websiteName" value="{{customWebsite.name}}"/> </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> + <actionGroup ref="AdminDeleteAllCustomerActionGroup" stepKey="deleteAllCustomers"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value=""/> </actionGroup> </after> - <!--Get bundle product option.--> <amOnPage url="{{AdminProductEditPage.url($bundleProduct.id$)}}" stepKey="openBundleProductEditPage"/> <grabTextFrom selector="{{AdminProductFormBundleSection.currentBundleOption}}" stepKey="grabBundleOption"/> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateCreditMemoWholeOrderWithConfigurableProductFromCustomSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateCreditMemoWholeOrderWithConfigurableProductFromCustomSourceTest.xml index 02e3ca62258..0d410e3b80b 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreateCreditMemoWholeOrderWithConfigurableProductFromCustomSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateCreditMemoWholeOrderWithConfigurableProductFromCustomSourceTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <!--Create test data.--> <createData entity="SimpleSubCategory" stepKey="category"/> @@ -75,6 +74,7 @@ <deleteData createDataKey="configurableProduct" stepKey="deleteConfigurableProduct"/> <deleteData createDataKey="configurableChildProduct" stepKey="deleteConfigurableProductVariation"/> <deleteData createDataKey="configurableProductAttribute" stepKey="deleteConfigurableProductAttribute"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Assign Default Stock to Main Website.--> <actionGroup ref="AssignWebsiteToStockActionGroup" stepKey="assignMainWebsiteToDefaultStock"> <argument name="stockName" value="{{_defaultStock.name}}"/> @@ -91,7 +91,6 @@ <argument name="indices" value=""/> </actionGroup> </after> - <!--Add configurable product to cart.--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$customer$" /> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateFullInvoiceForOrderWithSimpleProductOnCustomStockTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateFullInvoiceForOrderWithSimpleProductOnCustomStockTest.xml index 0bfb651596d..b8dc8ada88d 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreateFullInvoiceForOrderWithSimpleProductOnCustomStockTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateFullInvoiceForOrderWithSimpleProductOnCustomStockTest.xml @@ -17,7 +17,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <!--Create test data.--> <createData entity="SimpleSubCategory" stepKey="category"/> @@ -51,12 +50,12 @@ </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableAllSources"/> + <actionGroup ref="AdminDeleteAllCustomerActionGroup" stepKey="deleteAllCustomers"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value=""/> </actionGroup> </after> - <!--- Place order and create invoice. --> <actionGroup ref="AdminNavigateToNewOrderPageNewCustomerActionGroup" stepKey="createNewOrderForCustomer"/> <waitForPageLoad stepKey="waitForPageLoad"/> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateOrderWithDownloadableProductDefaultStockTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateOrderWithDownloadableProductDefaultStockTest.xml index bf53fc410aa..f69b63a72c7 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreateOrderWithDownloadableProductDefaultStockTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateOrderWithDownloadableProductDefaultStockTest.xml @@ -66,6 +66,9 @@ <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer" /> <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <deleteData createDataKey="customStock" stepKey="deleteCustomStock"/> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value=""/> </actionGroup> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreatePartialShipmentForOrderWithSimpleProductFromCustomSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreatePartialShipmentForOrderWithSimpleProductFromCustomSourceTest.xml index e98aaaa43a3..d4b162c960c 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreatePartialShipmentForOrderWithSimpleProductFromCustomSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreatePartialShipmentForOrderWithSimpleProductFromCustomSourceTest.xml @@ -84,6 +84,8 @@ <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer" /> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> </after> <!-- Login as Customer and add Product to Cart--> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateShipmentForWholeOrderWithConfigurableProductFromCustomSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateShipmentForWholeOrderWithConfigurableProductFromCustomSourceTest.xml index ef8ae624489..ddd8693e26b 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreateShipmentForWholeOrderWithConfigurableProductFromCustomSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateShipmentForWholeOrderWithConfigurableProductFromCustomSourceTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <!--Create test data.--> <createData entity="SimpleSubCategory" stepKey="category"/> @@ -78,6 +77,7 @@ <deleteData createDataKey="configurableProduct" stepKey="deleteConfigurableProduct"/> <deleteData createDataKey="configurableChildProduct" stepKey="deleteConfigurableProductVariation"/> <deleteData createDataKey="configurableProductAttribute" stepKey="deleteConfigurableProductAttribute"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Assign Default Stock to Main Website.--> <actionGroup ref="AssignWebsiteToStockActionGroup" stepKey="assignMainWebsiteToDefaultStock"> <argument name="stockName" value="{{_defaultStock.name}}"/> @@ -94,7 +94,6 @@ <argument name="indices" value=""/> </actionGroup> </after> - <!--Add configurable product to cart.--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$customer$" /> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminCreateShipmentForWholeOrderWithConfigurableProductFromDefaultSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminCreateShipmentForWholeOrderWithConfigurableProductFromDefaultSourceTest.xml index 959a4663b1f..6dbf65d6cb9 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminCreateShipmentForWholeOrderWithConfigurableProductFromDefaultSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminCreateShipmentForWholeOrderWithConfigurableProductFromDefaultSourceTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <!--Create test data.--> <createData entity="SimpleSubCategory" stepKey="category"/> @@ -61,6 +60,7 @@ <deleteData createDataKey="configurableProduct" stepKey="deleteConfigurableProduct"/> <deleteData createDataKey="configurableChildProduct" stepKey="deleteConfigurableProductVariation"/> <deleteData createDataKey="configurableProductAttribute" stepKey="deleteConfigurableProductAttribute"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Disable source.--> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableSources"/> <deleteData createDataKey="category" stepKey="deletecategory"/> @@ -71,7 +71,6 @@ <argument name="indices" value=""/> </actionGroup> </after> - <!--Add configurable product to cart.--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$customer$" /> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithDropDownAttributeViaTheAdminTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithDropDownAttributeViaTheAdminTest.xml index 4fabd42c6bf..e37fb450d13 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithDropDownAttributeViaTheAdminTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithDropDownAttributeViaTheAdminTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <createData entity="Msi_US_Customer" stepKey="createCustomer1"/> <createData entity="BasicMsiStockWithMainWebsite1" stepKey="createStock1"/> @@ -27,7 +26,6 @@ <createData entity="FullSource1" stepKey="createSource3"/> <createData entity="FullSource2" stepKey="createSource4"/> <createData entity="SimpleSubCategory" stepKey="simpleCategory1"/> - <createData entity="SourceStockLinked1" stepKey="linkSourceStock1"> <requiredEntity createDataKey="createStock1"/> <requiredEntity createDataKey="createSource1"/> @@ -44,7 +42,6 @@ <requiredEntity createDataKey="createStock1"/> <requiredEntity createDataKey="createSource4"/> </createData> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> </before> <after> @@ -53,53 +50,45 @@ <argument name="stockName" value="{{_defaultStock.name}}"/> <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> + <amOnPage url="{{AdminManageStockPage.url}}" stepKey="navigateToStockIndexPage"/> <actionGroup ref="AdminGridFilterResetActionGroup" stepKey="resetStocksGridFilter"/> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductsGridFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin1"/> <deleteData createDataKey="simpleCategory1" stepKey="deleteCategory1"/> + <deleteData createDataKey="createCustomer1" stepKey="deleteCustomer"/> </after> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnTheProductGridPage1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickOnTheAddProductToggle1"/> <click selector="{{AdminProductGridActionSection.addTypeProduct('configurable')}}" stepKey="clickOnAddConfigurableProduct1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad2"/> - <fillField userInput="{{ConfigurableMsiProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillProductName1"/> <fillField userInput="{{ConfigurableMsiProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillProductPrice1"/> <fillField userInput="{{ConfigurableMsiProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="fillProductSku1"/> <fillField userInput="{{ConfigurableMsiProduct.quantity}}" selector="{{AdminConfigurableProductFormSection.productQuantity}}" stepKey="fillProductQuantity1"/> <fillField userInput="{{ConfigurableMsiProduct.weight}}" selector="{{AdminConfigurableProductFormSection.productWeight}}" stepKey="fillProductWeight1"/> - <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$simpleCategory1.name$]" stepKey="searchAndSelectCategory1"/> <waitForPageLoad stepKey="waitForPageLoad3"/> - <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickOnTheCreateConfigurationsButton1"/> <waitForElementVisible selector="{{AdminConfigurableProductSelectAttributesSlideOut.grid}}" time="30" stepKey="waitForGridPresent1"/> - <waitForPageLoad stepKey="waitForPageLoad4"/> <click selector="{{AdminGridRow.checkboxByValue('color')}}" stepKey="clickOnTheColorAttribute1"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> - <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue1"/> <fillField userInput="{{colorProductAttribute1.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillNameForNewAttribute1"/> <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute1"/> - <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue2"/> <fillField userInput="{{colorProductAttribute2.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillNameForNewAttribute2"/> <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute2"/> - <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue3"/> <fillField userInput="{{colorProductAttribute3.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillNameForNewAttribute3"/> <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute3"/> - <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> - <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku1"/> - <click selector="{{AdminConfigurableProductAssignSourcesSlideOut.assignSources}}" stepKey="clickOnAssignSources2"/> - <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clickClearFilters1"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults1"> <argument name="selector" value="AdminManageSourcesGridFilterControls.code"/> @@ -107,105 +96,75 @@ </actionGroup> <click selector="{{AdminGridRow.checkboxByValue($createSource1.source[name]$)}}" stepKey="clickOnTheCheckboxForSource1"/> <click selector="{{AdminConfigurableProductAssignSourcesSlideOut.done}}" stepKey="clickOnDone2"/> - <fillField selector="{{AdminConfigurableProductAssignSourcesSlideOut.quantityPerSourceForMultiMode}}" userInput="100" stepKey="fillTheQuantityField1"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton5"/> - <waitForLoadingMaskToDisappear stepKey="waitUntilMaskDisappear" /> <waitForPageLoad stepKey="waitUntilPageIsReady" /> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> - <conditionalClick selector="button[data-index='confirm_button']" dependentSelector="button[data-index='confirm_button']" visible="true" stepKey="clickOnConfirmButton1"/> <waitForLoadingMaskToDisappear stepKey="waitUntilMaskDisappear1" /> <waitForPageLoad stepKey="waitUntilPageIsReady1" /> <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage1"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad5"/> - <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickOnCreateNewOrder1"/> - <fillField selector="#sales_order_create_customer_grid_filter_email" userInput="$createCustomer1.customer[email]$" stepKey="enterTheEmailAddress1"/> <click selector=".admin__filter-actions button[title='Search']" stepKey="clickOnSearch1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad6"/> - <click selector="#sales_order_create_customer_grid_table tbody tr" stepKey="clickOnTheFirstRow1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad7"/> - <conditionalClick selector="#store_1" dependentSelector="#order-store-selector" visible="true" stepKey="clickOnDefaultStoreViewIfPresent1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad20"/> - <click selector="#add_products" stepKey="clickOnAddProductsButton1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad8"/> - <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{colorProductAttribute2.name}}" stepKey="fillProductSkuField1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad9"/> <click selector="#order-search button[title='Search']" stepKey="clickOnSearch2"/> <waitForPageLoad time="10" stepKey="waitForPageLoad10"/> - <click selector="#order-search tbody tr .col-sku" stepKey="clickOnSku1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad11"/> - <fillField selector="#order-search tr .qty" userInput="5" stepKey="fillProductQuantity2"/> <click selector="#order-search .action-add" stepKey="clickOnAddSelectedProductsToOrder1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad12"/> - <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerAddress"> <argument name="customer" value="$createCustomer1$"/> <argument name="address" value="US_Address_TX"/> </actionGroup> - <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> - <click selector=".admin__field-shipping-same-as-billing label" stepKey="clickOnSameAsBillingAddress1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad13"/> - <click selector="#order-shipping-method-summary a" stepKey="clickOnGetShippingMethodsAndRates1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad14"/> - <click selector=".admin__order-shipment-methods-options-list li label" stepKey="clickOnFixedShippingRate1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad15"/> - <click selector=".order-totals-actions .save" stepKey="clickOnSubmitOrder1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad16"/> - <seeElement selector="{{AdminOrderDetailsMessagesSection.successMessage}}" stepKey="seeSuccessMessage1"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage2"/> - <grabTextFrom selector=".page-title" stepKey="grabTheOrderId1"/> - <actionGroup ref="VerifyBasicOrderInformationActionGroup" stepKey="verifyOrderInformation"> <argument name="customer" value="$createCustomer1$"/> <argument name="shippingAddress" value="US_Address_TX"/> <argument name="billingAddress" value="US_Address_TX"/> </actionGroup> - <see selector="{{AdminOrderItemsOrderedSection.productSkuColumn}}" userInput="{{colorProductAttribute2.name}}" stepKey="seeSkuInItemsOrdered1"/> <see selector=".edit-order-table .qty-table" userInput="Ordered 5" stepKey="seeQuantity1"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage2"/> <waitForPageLoad time="30" stepKey="waitForPageLoad17"/> - <conditionalClick selector="{{AdminGridFilterControls.clearAll}}" dependentSelector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] .admin__data-grid-filters-current._show" visible="true" stepKey="clearTheFiltersIfPresent"/> - <fillField userInput="$grabTheOrderId1" selector="{{OrdersGridSection.search}}" stepKey="fillCodeField2"/> <click selector=".data-grid-search-control-wrap button" stepKey="clickOnApplyFilters1"/> <waitForPageLoad time="5" stepKey="waitForPageLoad18"/> - <see selector="{{AdminGridRow.rowOne}}" userInput="$createCustomer1.firstname$" stepKey="seeFirstName1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="$createCustomer1.lastname$" stepKey="seeLastName1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="Pending" stepKey="seeFirstName3"/> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex2"/> <waitForPageLoad time="30" stepKey="waitForPageLoad19"/> - <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults3"> <argument name="selector" value="AdminProductGridFilterSection.skuFilter"/> <argument name="value" value="{{colorProductAttribute2.name}}"/> </actionGroup> - <see selector="{{AdminGridRow.rowOne}}" userInput="$createStock1.stock[name]$" stepKey="seeUpdatedQuantity1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="95" stepKey="seeUpdatedQuantity3"/> </test> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithSwatchAttributeViaTheAdminTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithSwatchAttributeViaTheAdminTest.xml index 07e8e263b87..27b4eafec12 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithSwatchAttributeViaTheAdminTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithSwatchAttributeViaTheAdminTest.xml @@ -53,6 +53,12 @@ <argument name="stockName" value="{{_defaultStock.name}}"/> <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> + <!-- Delete customer created during test execution --> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomerFromGrid"> + <argument name="customerEmail" value="MsiCustomer1.email"/> + </actionGroup> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin1"/> <deleteData createDataKey="simpleCategory1" stepKey="deleteCategory1"/> </after> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithTextAttributeViaTheAdminTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithTextAttributeViaTheAdminTest.xml index fc045bdc3c3..ff2ae913b6b 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithTextAttributeViaTheAdminTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedForGuestCustomerWithConfigurableProductWithTextAttributeViaTheAdminTest.xml @@ -20,7 +20,6 @@ <!-- Moving test out of PR suite until issue is fixed (AC-2806) --> <group value="pr_exclude" /> </annotations> - <before> <createData entity="BasicMsiStockWithMainWebsite1" stepKey="createStock1"/> <createData entity="FullSource1" stepKey="createSource1"/> @@ -28,12 +27,10 @@ <createData entity="FullSource1" stepKey="createSource3"/> <createData entity="FullSource2" stepKey="createSource4"/> <createData entity="SimpleSubCategory" stepKey="simpleCategory1"/> - <createData entity="SourceStockLinked1" stepKey="linkSourceStock1"> <requiredEntity createDataKey="createStock1"/> <requiredEntity createDataKey="createSource1"/> </createData> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> </before> <after> @@ -44,71 +41,61 @@ </actionGroup> <!-- Delete configurable product created via UI --> <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> + <!-- Delete customer created during test execution --> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomerFromGrid"> + <argument name="customerEmail" value="MsiCustomer1.email"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin1"/> <deleteData createDataKey="simpleCategory1" stepKey="deleteCategory1"/> </after> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnTheProductGridPage1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickOnTheAddProductToggle1"/> <click selector="{{AdminProductGridActionSection.addTypeProduct('configurable')}}" stepKey="clickOnAddConfigurableProduct1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad2"/> - <fillField userInput="{{ConfigurableMsiProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillProductName1"/> <fillField userInput="{{ConfigurableMsiProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillProductPrice1"/> <fillField userInput="{{ConfigurableMsiProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="fillProductSku1"/> <fillField userInput="{{ConfigurableMsiProduct.quantity}}" selector="{{AdminConfigurableProductFormSection.productQuantity}}" stepKey="fillProductQuantity1"/> <fillField userInput="{{ConfigurableMsiProduct.weight}}" selector="{{AdminConfigurableProductFormSection.productWeight}}" stepKey="fillProductWeight1"/> - <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$simpleCategory1.name$$]" stepKey="searchAndSelectCategory1"/> <waitForPageLoad stepKey="waitForPageLoad3"/> <waitForElementVisible selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" time ="100" stepKey="waitForElementGridPresent1"></waitForElementVisible> <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickOnTheCreateConfigurationsButton1"/> <waitForElementVisible selector="{{AdminConfigurableProductSelectAttributesSlideOut.grid}}" time="30" stepKey="waitForGridPresent1"/> - <waitForPageLoad stepKey="waitForPageLoad4"/> <click selector="{{AdminCreateProductConfigurationsPanel.createNewAttribute}}" stepKey="clickOnCreateNewAttribute1"/> <waitForPageLoad stepKey="waitForPageLoad5"/> - <switchToIFrame selector="{{AdminNewAttributePanel.newAttributeIFrame}}" stepKey="switchToNewAttributeIFrame1"/> <fillField selector="{{AdminNewAttributePanel.defaultLabel}}" userInput="{{visualSwatchAttribute.name}}" stepKey="fillDefaultLabel1"/> <selectOption selector="#frontend_input" userInput="{{textSwatchAttribute.catalogInputType}}" stepKey="fillCatalogInputTypeForStoreOwner1"/> - <click selector="#add_new_swatch_text_option_button" stepKey="clickOnAddSwatch1"/> <waitForPageLoad stepKey="waitForPageLoad6"/> - <fillField selector="input[name='swatchtext[value][option_0][0]']" userInput="{{textSwatch1.name}}" stepKey="fillAdminName1"/> <fillField selector="input[name='optiontext[value][option_0][0]']" userInput="{{textSwatch1.name}}" stepKey="fillDescription1"/> <fillField selector="input[name='swatchtext[value][option_0][1]']" userInput="{{textSwatch1.name}}" stepKey="fillAdminName2"/> <fillField selector="input[name='optiontext[value][option_0][1]']" userInput="{{textSwatch1.name}}" stepKey="fillDescription2"/> - <click selector="#add_new_swatch_text_option_button" stepKey="clickOnAddSwatch2"/> <waitForPageLoad stepKey="waitForPageLoad7"/> - <fillField selector="input[name='swatchtext[value][option_1][0]']" userInput="{{textSwatch2.name}}" stepKey="fillAdminName3"/> <fillField selector="input[name='optiontext[value][option_1][0]']" userInput="{{textSwatch2.name}}" stepKey="fillDescription3"/> <fillField selector="input[name='swatchtext[value][option_1][1]']" userInput="{{textSwatch2.name}}" stepKey="fillAdminName4"/> <fillField selector="input[name='optiontext[value][option_1][1]']" userInput="{{textSwatch2.name}}" stepKey="fillDescription4"/> - <click selector="{{AdminNewAttributePanel.saveAttribute}}" stepKey="clickOnNewAttributePanel1"/> <waitForPageLoad stepKey="waitForPageLoad8"/> <switchToIFrame stepKey="switchOutOfIFrame1"/> <waitForPageLoad stepKey="waitForPageLoad9"/> - <conditionalClick selector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" dependentSelector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" visible="true" stepKey="clickClearFilters1"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults1"> <argument name="selector" value="AdminAssignProductAttributeSlideOutSection.attributeLabel"/> <argument name="value" value="{{visualSwatchAttribute.name}}"/> </actionGroup> - <click selector="{{AdminGridRow.checkboxByValue(visualSwatchAttribute.name)}}" stepKey="clickOnTheColorAttribute1"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll1"/> - <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku1"/> <click selector="{{AdminConfigurableProductAssignSourcesSlideOut.assignSources}}" stepKey="clickOnAssignSources2"/> - <conditionalClick selector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" dependentSelector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" visible="true" stepKey="clickClearFilters2"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults2"> <argument name="selector" value="AdminManageSourcesGridFilterControls.code"/> @@ -116,102 +103,73 @@ </actionGroup> <click selector="{{AdminGridRow.checkboxByValue($$createSource1.source[name]$$)}}" stepKey="clickOnTheCheckboxForSource2"/> <click selector="{{AdminConfigurableProductAssignSourcesSlideOut.done}}" stepKey="clickOnDone3"/> - <fillField selector="{{AdminConfigurableProductAssignSourcesSlideOut.quantityPerSourceForMultiMode}}" userInput="100" stepKey="fillTheQuantityField1"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton5"/> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> <click selector="button[data-index='confirm_button']" stepKey="clickOnConfirm1"/> <waitForPageLoad stepKey="waitForPageLoad10"/> <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage1"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad11"/> - <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickOnCreateNewOrder1"/> - <click selector="button[title='Create New Customer']" stepKey="clickOnCreateNewCustomer1"/> <waitForPageLoad stepKey="waitForPageLoad12"/> - <conditionalClick selector="#store_1" dependentSelector="#order-store-selector" visible="true" stepKey="clickOnDefaultStoreViewIfPresent1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad27"/> - <fillField selector="#email" userInput="{{MsiCustomer1.email}}" stepKey="fillEmailAddress1"/> - <click selector="#add_products" stepKey="clickOnAddProductsButton1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad15"/> - <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{textSwatch1.name}}" stepKey="fillProductSkuField1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad16"/> <click selector="#order-search button[title='Search']" stepKey="clickOnSearch2"/> <waitForPageLoad time="10" stepKey="waitForPageLoad17"/> - <click selector="#order-search tbody tr .col-sku" stepKey="clickOnSku1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad18"/> - <fillField selector="#order-search tr .qty" userInput="5" stepKey="fillProductQuantity2"/> <click selector="#order-search .action-add" stepKey="clickOnAddSelectedProductsToOrder1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad19"/> - <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerAddress"> <argument name="customer" value="MsiCustomer1"/> <argument name="address" value="US_Address_TX"/> </actionGroup> - <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> - <click selector=".admin__field-shipping-same-as-billing label" stepKey="clickOnSameAsBillingAddress1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad20"/> - <click selector="#order-shipping-method-summary a" stepKey="clickOnGetShippingMethodsAndRates1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad21"/> - <click selector=".admin__order-shipment-methods-options-list li label" stepKey="clickOnFixedShippingRate1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad22"/> - <click selector=".order-totals-actions .save" stepKey="clickOnSubmitOrder1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad23"/> - <seeElement selector="{{AdminOrderDetailsMessagesSection.successMessage}}" stepKey="seeSuccessMessage1"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage2"/> - <grabTextFrom selector=".page-title" stepKey="grabTheOrderId1"/> - <actionGroup ref="VerifyBasicOrderInformationActionGroup" stepKey="verifyOrderInformation"> <argument name="customer" value="MsiCustomer1"/> <argument name="shippingAddress" value="US_Address_TX"/> <argument name="billingAddress" value="US_Address_TX"/> </actionGroup> - <see selector="{{AdminOrderItemsOrderedSection.productSkuColumn}}" userInput="{{textSwatch1.name}}" stepKey="seeSkuInItemsOrdered1"/> <see selector=".edit-order-table .qty-table" userInput="Ordered 5" stepKey="seeQuantity1"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage2"/> <waitForPageLoad time="30" stepKey="waitForPageLoad24"/> - <conditionalClick selector="{{AdminGridFilterControls.clearAll}}" dependentSelector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] .admin__data-grid-filters-current._show" visible="true" stepKey="clearTheFiltersIfPresent"/> - <fillField userInput="$grabTheOrderId1" selector="{{OrdersGridSection.search}}" stepKey="fillCodeField2"/> <click selector=".data-grid-search-control-wrap button" stepKey="clickOnApplyFilters1"/> <waitForPageLoad time="5" stepKey="waitForPageLoad25"/> - <see selector="{{AdminGridRow.rowOne}}" userInput="{{MsiCustomer1.firstname}}" stepKey="seeFirstName1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="{{MsiCustomer1.lastname}}" stepKey="seeLastName1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="Pending" stepKey="seeFirstName3"/> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex2"/> <waitForPageLoad time="30" stepKey="waitForPageLoad26"/> - <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults3"> <argument name="selector" value="AdminProductGridFilterSection.skuFilter"/> <argument name="value" value="{{textSwatch1.name}}"/> </actionGroup> - <conditionalClick selector="{{AdminGridColumnsControls.columns}}" dependentSelector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] .admin__data-grid-action-columns-menu" visible="false" stepKey="clickOnColumnsIfNotOpen1"/> <click selector="{{AdminGridColumnsControls.reset}}" stepKey="clickOnResetToRestoreDefaultColumns1"/> <conditionalClick selector="{{AdminGridColumnsControls.columns}}" dependentSelector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] .admin__data-grid-action-columns-menu" visible="false" stepKey="clickOnColumnsIfNotOpen2"/> - <see selector="{{AdminGridRow.rowOne}}" userInput="$$createStock1.stock[name]$$" stepKey="seeUpdatedQuantity1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="95" stepKey="seeUpdatedQuantity3"/> </test> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedWithConfigurableProductWithSwatchAttributeViaTheAdminTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedWithConfigurableProductWithSwatchAttributeViaTheAdminTest.xml index c526fb61670..7196cec9052 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedWithConfigurableProductWithSwatchAttributeViaTheAdminTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedWithConfigurableProductWithSwatchAttributeViaTheAdminTest.xml @@ -20,7 +20,6 @@ <!-- Moving test out of PR suite until issue is fixed (AC-2806) --> <group value="pr_exclude" /> </annotations> - <before> <createData entity="Msi_US_Customer" stepKey="createCustomer1"/> <createData entity="BasicMsiStockWithMainWebsite1" stepKey="createStock1"/> @@ -29,7 +28,6 @@ <createData entity="FullSource1" stepKey="createSource3"/> <createData entity="FullSource2" stepKey="createSource4"/> <createData entity="SimpleSubCategory" stepKey="simpleCategory1"/> - <createData entity="SourceStockLinked1" stepKey="linkSourceStock1"> <requiredEntity createDataKey="createStock1"/> <requiredEntity createDataKey="createSource1"/> @@ -46,7 +44,6 @@ <requiredEntity createDataKey="createStock1"/> <requiredEntity createDataKey="createSource4"/> </createData> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> </before> <after> @@ -55,70 +52,59 @@ <argument name="stockName" value="{{_defaultStock.name}}"/> <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin1"/> <deleteData createDataKey="simpleCategory1" stepKey="deleteCategory1"/> + <deleteData createDataKey="createCustomer1" stepKey="deleteCustomer"/> </after> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnTheProductGridPage1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickOnTheAddProductToggle1"/> <click selector="{{AdminProductGridActionSection.addTypeProduct('configurable')}}" stepKey="clickOnAddConfigurableProduct1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad2"/> - <fillField userInput="{{ConfigurableMsiProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillProductName1"/> <fillField userInput="{{ConfigurableMsiProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillProductPrice1"/> <fillField userInput="{{ConfigurableMsiProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="fillProductSku1"/> <fillField userInput="{{ConfigurableMsiProduct.quantity}}" selector="{{AdminConfigurableProductFormSection.productQuantity}}" stepKey="fillProductQuantity1"/> <fillField userInput="{{ConfigurableMsiProduct.weight}}" selector="{{AdminConfigurableProductFormSection.productWeight}}" stepKey="fillProductWeight1"/> - <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$simpleCategory1.name$$]" stepKey="searchAndSelectCategory1"/> <waitForPageLoad stepKey="waitForPageLoad3"/> <waitForLoadingMaskToDisappear stepKey="waitForGridLoadMaskToDisappear"/> <waitForElementClickable selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="waitForTheButtonCreateConfigurationsButtonToBeClickable"/> <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickOnTheCreateConfigurationsButton1"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear"/> - <waitForElementVisible selector="{{AdminConfigurableProductSelectAttributesSlideOut.grid}}" time="30" stepKey="waitForGridPresent1"/> - <waitForPageLoad stepKey="waitForPageLoad4"/> <click selector="{{AdminCreateProductConfigurationsPanel.createNewAttribute}}" stepKey="clickOnCreateNewAttribute1"/> <waitForPageLoad stepKey="waitForPageLoad5"/> - <switchToIFrame selector="{{AdminNewAttributePanel.newAttributeIFrame}}" stepKey="switchToNewAttributeIFrame1"/> <fillField selector="{{AdminNewAttributePanel.defaultLabel}}" userInput="{{visualSwatchAttribute.name}}" stepKey="fillDefaultLabel1"/> <selectOption selector="#frontend_input" userInput="{{visualSwatchAttribute.catalogInputType}}" stepKey="fillCatalogInputTypeForStoreOwner1"/> - <click selector="#add_new_swatch_visual_option_button" stepKey="clickOnAddSwatch1"/> <waitForPageLoad stepKey="waitForPageLoad6"/> - <fillField selector="input[name='optionvisual[value][option_0][0]']" userInput="{{visualSwatchColor1.name}}" stepKey="fillAdminName1"/> <fillField selector="input[name='optionvisual[value][option_0][1]']" userInput="{{visualSwatchColor1.name}}" stepKey="fillAdminName2"/> - <click selector="#add_new_swatch_visual_option_button" stepKey="clickOnAddSwatch2"/> <waitForPageLoad stepKey="waitForPageLoad7"/> - <fillField selector="input[name='optionvisual[value][option_1][0]']" userInput="{{visualSwatchColor2.name}}" stepKey="fillAdminName3"/> <fillField selector="input[name='optionvisual[value][option_1][1]']" userInput="{{visualSwatchColor2.name}}" stepKey="fillAdminName4"/> - <click selector="{{AdminNewAttributePanel.saveAttribute}}" stepKey="clickOnNewAttributePanel1"/> <waitForPageLoad stepKey="waitForPageLoad8"/> <switchToIFrame stepKey="switchOutOfIFrame1"/> <waitForPageLoad stepKey="waitForPageLoad9"/> - <conditionalClick selector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" dependentSelector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" visible="true" stepKey="clickClearFilters0"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults0"> <argument name="selector" value="AdminAssignProductAttributeSlideOutSection.attributeLabel"/> <argument name="value" value="{{visualSwatchAttribute.name}}"/> </actionGroup> - <click selector="{{AdminGridRow.checkboxByValue(visualSwatchAttribute.name)}}" stepKey="clickOnTheColorAttribute1"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll1"/> - <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku1"/> <click selector="{{AdminConfigurableProductAssignSourcesSlideOut.assignSources}}" stepKey="clickOnAssignSources2"/> - <conditionalClick selector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" dependentSelector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" visible="true" stepKey="clickClearFilters1"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults1"> <argument name="selector" value="AdminManageSourcesGridFilterControls.code"/> @@ -126,100 +112,72 @@ </actionGroup> <click selector="{{AdminGridRow.checkboxByValue($$createSource1.source[name]$$)}}" stepKey="clickOnTheCheckboxForSource1"/> <click selector="{{AdminConfigurableProductAssignSourcesSlideOut.done}}" stepKey="clickOnDone2"/> - <fillField selector="{{AdminConfigurableProductAssignSourcesSlideOut.quantityPerSourceForMultiMode}}" userInput="100" stepKey="fillTheQuantityField1"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton5"/> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> <click selector="button[data-index='confirm_button']" stepKey="clickOnConfirm1"/> <waitForPageLoad stepKey="waitForPageLoad10"/> <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage1"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad11"/> - <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickOnCreateNewOrder1"/> - <fillField selector="#sales_order_create_customer_grid_filter_email" userInput="$$createCustomer1.customer[email]$$" stepKey="enterTheEmailAddress1"/> <click selector=".admin__filter-actions button[title='Search']" stepKey="clickOnSearch1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad12"/> - <click selector="#sales_order_create_customer_grid_table tbody tr" stepKey="clickOnTheFirstRow1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad13"/> - <conditionalClick selector="#store_1" dependentSelector="#order-store-selector" visible="true" stepKey="clickOnDefaultStoreViewIfPresent1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad14"/> - <click selector="#add_products" stepKey="clickOnAddProductsButton1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad15"/> - <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{visualSwatchColor2.name}}" stepKey="fillProductSkuField1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad16"/> <click selector="#order-search button[title='Search']" stepKey="clickOnSearch2"/> <waitForPageLoad time="10" stepKey="waitForPageLoad17"/> - <click selector="#order-search tbody tr .col-sku" stepKey="clickOnSku1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad18"/> - <fillField selector="#order-search tr .qty" userInput="5" stepKey="fillProductQuantity2"/> <click selector="#order-search .action-add" stepKey="clickOnAddSelectedProductsToOrder1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad19"/> - <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerAddress"> <argument name="customer" value="$$createCustomer1$$"/> <argument name="address" value="US_Address_TX"/> </actionGroup> - <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> - <click selector=".admin__field-shipping-same-as-billing label" stepKey="clickOnSameAsBillingAddress1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad20"/> - <click selector="#order-shipping-method-summary a" stepKey="clickOnGetShippingMethodsAndRates1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad21"/> - <click selector=".admin__order-shipment-methods-options-list li label" stepKey="clickOnFixedShippingRate1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad22"/> - <click selector=".order-totals-actions .save" stepKey="clickOnSubmitOrder1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad23"/> - <seeElement selector="{{AdminOrderDetailsMessagesSection.successMessage}}" stepKey="seeSuccessMessage1"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage2"/> - <grabTextFrom selector=".page-title" stepKey="grabTheOrderId1"/> - <actionGroup ref="VerifyBasicOrderInformationActionGroup" stepKey="verifyOrderInformation"> <argument name="customer" value="$$createCustomer1$$"/> <argument name="shippingAddress" value="US_Address_TX"/> <argument name="billingAddress" value="US_Address_TX"/> </actionGroup> - <see selector="{{AdminOrderItemsOrderedSection.productSkuColumn}}" userInput="{{visualSwatchColor2.name}}" stepKey="seeSkuInItemsOrdered1"/> <see selector=".edit-order-table .qty-table" userInput="Ordered 5" stepKey="seeQuantity1"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage2"/> <waitForPageLoad time="30" stepKey="waitForPageLoad24"/> - <conditionalClick selector="{{AdminGridFilterControls.clearAll}}" dependentSelector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] .admin__data-grid-filters-current._show" visible="true" stepKey="clearTheFiltersIfPresent"/> - <fillField userInput="$grabTheOrderId1" selector="{{OrdersGridSection.search}}" stepKey="fillCodeField2"/> <click selector=".data-grid-search-control-wrap button" stepKey="clickOnApplyFilters1"/> <waitForPageLoad time="5" stepKey="waitForPageLoad25"/> - <see selector="{{AdminGridRow.rowOne}}" userInput="$$createCustomer1.customer[firstname]$$" stepKey="seeFirstName1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="$$createCustomer1.customer[lastname]$$" stepKey="seeLastName1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="Pending" stepKey="seeFirstName3"/> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex2"/> <waitForPageLoad time="30" stepKey="waitForPageLoad26"/> - <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults3"> <argument name="selector" value="AdminProductGridFilterSection.skuFilter"/> <argument name="value" value="{{visualSwatchColor2.name}}"/> </actionGroup> - <see selector="{{AdminGridRow.rowOne}}" userInput="$$createStock1.stock[name]$$" stepKey="seeUpdatedQuantity1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="95" stepKey="seeUpdatedQuantity3"/> </test> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedWithConfigurableProductWithTextAttributeViaTheAdminTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedWithConfigurableProductWithTextAttributeViaTheAdminTest.xml index 25e79d06b77..058082c2c8e 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedWithConfigurableProductWithTextAttributeViaTheAdminTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminOrderCreatedWithConfigurableProductWithTextAttributeViaTheAdminTest.xml @@ -20,7 +20,6 @@ <!-- Moving test out of PR suite until issue is fixed (AC-2806) --> <group value="pr_exclude" /> </annotations> - <before> <createData entity="Msi_US_Customer" stepKey="createCustomer1"/> <createData entity="BasicMsiStockWithMainWebsite1" stepKey="createStock1"/> @@ -29,7 +28,6 @@ <createData entity="FullSource1" stepKey="createSource3"/> <createData entity="FullSource2" stepKey="createSource4"/> <createData entity="SimpleSubCategory" stepKey="simpleCategory1"/> - <createData entity="SourceStockLinked1" stepKey="linkSourceStock1"> <requiredEntity createDataKey="createStock1"/> <requiredEntity createDataKey="createSource1"/> @@ -46,7 +44,6 @@ <requiredEntity createDataKey="createStock1"/> <requiredEntity createDataKey="createSource4"/> </createData> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> </before> <after> @@ -55,71 +52,60 @@ <argument name="stockName" value="{{_defaultStock.name}}"/> <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin1"/> <deleteData createDataKey="simpleCategory1" stepKey="deleteCategory1"/> + <deleteData createDataKey="createCustomer1" stepKey="deleteCustomer"/> </after> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnTheProductGridPage1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickOnTheAddProductToggle1"/> <click selector="{{AdminProductGridActionSection.addTypeProduct('configurable')}}" stepKey="clickOnAddConfigurableProduct1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad2"/> - <fillField userInput="{{ConfigurableMsiProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillProductName1"/> <fillField userInput="{{ConfigurableMsiProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillProductPrice1"/> <fillField userInput="{{ConfigurableMsiProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="fillProductSku1"/> <fillField userInput="{{ConfigurableMsiProduct.quantity}}" selector="{{AdminConfigurableProductFormSection.productQuantity}}" stepKey="fillProductQuantity1"/> <fillField userInput="{{ConfigurableMsiProduct.weight}}" selector="{{AdminConfigurableProductFormSection.productWeight}}" stepKey="fillProductWeight1"/> - <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$simpleCategory1.name$$]" stepKey="searchAndSelectCategory1"/> <waitForPageLoad stepKey="waitForPageLoad3"/> - <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickOnTheCreateConfigurationsButton1"/> <waitForElementVisible selector="{{AdminConfigurableProductSelectAttributesSlideOut.grid}}" time="30" stepKey="waitForGridPresent1"/> - <waitForPageLoad stepKey="waitForPageLoad4"/> <click selector="{{AdminCreateProductConfigurationsPanel.createNewAttribute}}" stepKey="clickOnCreateNewAttribute1"/> <waitForPageLoad stepKey="waitForPageLoad5"/> - <switchToIFrame selector="{{AdminNewAttributePanel.newAttributeIFrame}}" stepKey="switchToNewAttributeIFrame1"/> <fillField selector="{{AdminNewAttributePanel.defaultLabel}}" userInput="{{visualSwatchAttribute.name}}" stepKey="fillDefaultLabel1"/> <selectOption selector="#frontend_input" userInput="{{textSwatchAttribute.catalogInputType}}" stepKey="fillCatalogInputTypeForStoreOwner1"/> - <click selector="#add_new_swatch_text_option_button" stepKey="clickOnAddSwatch1"/> <waitForPageLoad stepKey="waitForPageLoad6"/> - <fillField selector="input[name='swatchtext[value][option_0][0]']" userInput="{{textSwatch1.name}}" stepKey="fillAdminName1"/> <fillField selector="input[name='optiontext[value][option_0][0]']" userInput="{{textSwatch1.name}}" stepKey="fillDescription1"/> <fillField selector="input[name='swatchtext[value][option_0][1]']" userInput="{{textSwatch1.name}}" stepKey="fillAdminName2"/> <fillField selector="input[name='optiontext[value][option_0][1]']" userInput="{{textSwatch1.name}}" stepKey="fillDescription2"/> - <click selector="#add_new_swatch_text_option_button" stepKey="clickOnAddSwatch2"/> <waitForPageLoad stepKey="waitForPageLoad7"/> - <fillField selector="input[name='swatchtext[value][option_1][0]']" userInput="{{textSwatch2.name}}" stepKey="fillAdminName3"/> <fillField selector="input[name='optiontext[value][option_1][0]']" userInput="{{textSwatch2.name}}" stepKey="fillDescription3"/> <fillField selector="input[name='swatchtext[value][option_1][1]']" userInput="{{textSwatch2.name}}" stepKey="fillAdminName4"/> <fillField selector="input[name='optiontext[value][option_1][1]']" userInput="{{textSwatch2.name}}" stepKey="fillDescription4"/> - <click selector="{{AdminNewAttributePanel.saveAttribute}}" stepKey="clickOnNewAttributePanel1"/> <waitForPageLoad stepKey="waitForPageLoad8"/> <switchToIFrame stepKey="switchOutOfIFrame1"/> <waitForPageLoad stepKey="waitForPageLoad9"/> - <conditionalClick selector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" dependentSelector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" visible="true" stepKey="clickClearFilters1"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults0"> <argument name="selector" value="AdminAssignProductAttributeSlideOutSection.attributeLabel"/> <argument name="value" value="{{visualSwatchAttribute.name}}"/> </actionGroup> - <click selector="{{AdminGridRow.checkboxByValue(visualSwatchAttribute.name)}}" stepKey="clickOnTheColorAttribute1"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll1"/> - <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku1"/> <click selector="{{AdminConfigurableProductAssignSourcesSlideOut.assignSources}}" stepKey="clickOnAssignSources2"/> - <conditionalClick selector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" dependentSelector="(//div[@class='admin__data-grid-header']//*[@data-action='grid-filter-reset'])[1]" visible="true" stepKey="clickClearFilters2"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults1"> <argument name="selector" value="AdminManageSourcesGridFilterControls.code"/> @@ -127,100 +113,72 @@ </actionGroup> <click selector="{{AdminGridRow.checkboxByValue($$createSource1.source[name]$$)}}" stepKey="clickOnTheCheckboxForSource1"/> <click selector="{{AdminConfigurableProductAssignSourcesSlideOut.done}}" stepKey="clickOnDone2"/> - <fillField selector="{{AdminConfigurableProductAssignSourcesSlideOut.quantityPerSourceForMultiMode}}" userInput="100" stepKey="fillTheQuantityField1"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton5"/> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> <click selector="button[data-index='confirm_button']" stepKey="clickOnConfirm1"/> <waitForPageLoad stepKey="waitForPageLoad10"/> <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage1"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad11"/> - <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickOnCreateNewOrder1"/> - <fillField selector="#sales_order_create_customer_grid_filter_email" userInput="$$createCustomer1.customer[email]$$" stepKey="enterTheEmailAddress1"/> <click selector=".admin__filter-actions button[title='Search']" stepKey="clickOnSearch1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad12"/> - <click selector="#sales_order_create_customer_grid_table tbody tr" stepKey="clickOnTheFirstRow1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad13"/> - <conditionalClick selector="#store_1" dependentSelector="#order-store-selector" visible="true" stepKey="clickOnDefaultStoreViewIfPresent1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad14"/> - <click selector="#add_products" stepKey="clickOnAddProductsButton1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad15"/> - <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{textSwatch1.name}}" stepKey="fillProductSkuField1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad16"/> <click selector="#order-search button[title='Search']" stepKey="clickOnSearch2"/> <waitForPageLoad time="10" stepKey="waitForPageLoad17"/> - <click selector="#order-search tbody tr .col-sku" stepKey="clickOnSku1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad18"/> - <fillField selector="#order-search tr .qty" userInput="5" stepKey="fillProductQuantity2"/> <click selector="#order-search .action-add" stepKey="clickOnAddSelectedProductsToOrder1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad19"/> - <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerAddress"> <argument name="customer" value="$$createCustomer1$$"/> <argument name="address" value="US_Address_TX"/> </actionGroup> - <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> - <click selector=".admin__field-shipping-same-as-billing label" stepKey="clickOnSameAsBillingAddress1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad20"/> - <click selector="#order-shipping-method-summary a" stepKey="clickOnGetShippingMethodsAndRates1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad21"/> - <click selector=".admin__order-shipment-methods-options-list li label" stepKey="clickOnFixedShippingRate1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad22"/> - <click selector=".order-totals-actions .save" stepKey="clickOnSubmitOrder1"/> <waitForPageLoad time="30" stepKey="waitForPageLoad23"/> - <seeElement selector="{{AdminOrderDetailsMessagesSection.successMessage}}" stepKey="seeSuccessMessage1"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage2"/> - <grabTextFrom selector=".page-title" stepKey="grabTheOrderId1"/> - <actionGroup ref="VerifyBasicOrderInformationActionGroup" stepKey="verifyOrderInformation"> <argument name="customer" value="$$createCustomer1$$"/> <argument name="shippingAddress" value="US_Address_TX"/> <argument name="billingAddress" value="US_Address_TX"/> </actionGroup> - <see selector="{{AdminOrderItemsOrderedSection.productSkuColumn}}" userInput="{{textSwatch1.name}}" stepKey="seeSkuInItemsOrdered1"/> <see selector=".edit-order-table .qty-table" userInput="Ordered 5" stepKey="seeQuantity1"/> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToOrdersPage2"/> <waitForPageLoad time="30" stepKey="waitForPageLoad24"/> - <conditionalClick selector="{{AdminGridFilterControls.clearAll}}" dependentSelector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] .admin__data-grid-filters-current._show" visible="true" stepKey="clearTheFiltersIfPresent"/> - <fillField userInput="$grabTheOrderId1" selector="{{OrdersGridSection.search}}" stepKey="fillCodeField2"/> <click selector=".data-grid-search-control-wrap button" stepKey="clickOnApplyFilters1"/> <waitForPageLoad time="5" stepKey="waitForPageLoad25"/> - <see selector="{{AdminGridRow.rowOne}}" userInput="$$createCustomer1.customer[firstname]$$" stepKey="seeFirstName1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="$$createCustomer1.customer[lastname]$$" stepKey="seeLastName1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="Pending" stepKey="seeFirstName3"/> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex2"/> <waitForPageLoad time="30" stepKey="waitForPageLoad26"/> - <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="filterSearchResults3"> <argument name="selector" value="AdminProductGridFilterSection.skuFilter"/> <argument name="value" value="{{textSwatch1.name}}"/> </actionGroup> - <see selector="{{AdminGridRow.rowOne}}" userInput="$$createStock1.stock[name]$$" stepKey="seeUpdatedQuantity1"/> <see selector="{{AdminGridRow.rowOne}}" userInput="95" stepKey="seeUpdatedQuantity3"/> </test> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminOrderWithTwoSimpleProductsOnTestStockCanceledFromAdminAfterPartialInvoiceAndPartialShipmentTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminOrderWithTwoSimpleProductsOnTestStockCanceledFromAdminAfterPartialInvoiceAndPartialShipmentTest.xml index 27c4b127969..e962464f0ad 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminOrderWithTwoSimpleProductsOnTestStockCanceledFromAdminAfterPartialInvoiceAndPartialShipmentTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminOrderWithTwoSimpleProductsOnTestStockCanceledFromAdminAfterPartialInvoiceAndPartialShipmentTest.xml @@ -54,6 +54,7 @@ <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer" /> <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> <deleteData createDataKey="simpleCategory" stepKey="deleteCategory"/> <deleteData createDataKey="additionalStock" stepKey="deleteCustomStock"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminReorderBundleProductCustomStockCustomWebsiteTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminReorderBundleProductCustomStockCustomWebsiteTest.xml index bb06a65f804..ab5a130b67b 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminReorderBundleProductCustomStockCustomWebsiteTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminReorderBundleProductCustomStockCustomWebsiteTest.xml @@ -17,7 +17,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <!--Create test data.--> <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> @@ -112,12 +111,12 @@ <argument name="websiteName" value="{{customWebsite.name}}"/> </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> + <actionGroup ref="AdminDeleteAllCustomerActionGroup" stepKey="deleteAllCustomers"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value=""/> </actionGroup> </after> - <!--Get bundle product option.--> <amOnPage url="{{AdminProductEditPage.url($bundleProduct.id$)}}" stepKey="openBundleProductEditPage"/> <grabTextFrom selector="{{AdminProductFormBundleSection.currentBundleOption}}" stepKey="grabBundleOption"/> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminSaveTwiceDuringCreatingConfigurableProductTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminSaveTwiceDuringCreatingConfigurableProductTest.xml index b35175e33cf..464eff76ea1 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminSaveTwiceDuringCreatingConfigurableProductTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminSaveTwiceDuringCreatingConfigurableProductTest.xml @@ -39,6 +39,9 @@ <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> <deleteData createDataKey="category" stepKey="deleteCategory"/> + <!-- Delete all products via API --> + <helper class="Magento\Catalog\Test\Mftf\Helper\ProductApiHelper" method="deleteAllProductsApi" stepKey="deleteAllProductsViaApi"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdminArea"/> </after> <comment userInput="Create Configurable product with two options 'In Stock' on 'Default' stock" diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminUserApplyToSeveralSimpleProductsMassActionUnAssignProductSourcesOnProductsGridToUnAssignAllAssignedSourcesTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminUserApplyToSeveralSimpleProductsMassActionUnAssignProductSourcesOnProductsGridToUnAssignAllAssignedSourcesTest.xml index b5e639ade27..1a50a23c4ff 100755 --- a/InventoryAdminUi/Test/Mftf/Test/AdminUserApplyToSeveralSimpleProductsMassActionUnAssignProductSourcesOnProductsGridToUnAssignAllAssignedSourcesTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminUserApplyToSeveralSimpleProductsMassActionUnAssignProductSourcesOnProductsGridToUnAssignAllAssignedSourcesTest.xml @@ -17,7 +17,6 @@ <severity value="CRITICAL"/> <group value="msi"/> <group value="multi_mode"/> - <group value="pr_exclude"/> </annotations> <before> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminUserCreateOrderFromCustomWebsiteWithSimpleProductWithManageStockNoInAdvancedInventorySettingsOnProductPageTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminUserCreateOrderFromCustomWebsiteWithSimpleProductWithManageStockNoInAdvancedInventorySettingsOnProductPageTest.xml index 9999fd0d859..33200a0f9ed 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminUserCreateOrderFromCustomWebsiteWithSimpleProductWithManageStockNoInAdvancedInventorySettingsOnProductPageTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminUserCreateOrderFromCustomWebsiteWithSimpleProductWithManageStockNoInAdvancedInventorySettingsOnProductPageTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <createData entity="FullSource1" stepKey="additionalSource"/> <createData entity="BasicMsiStock1" stepKey="additionalStock"/> @@ -29,7 +28,6 @@ <createData entity="SimpleSubCategory" stepKey="createCategory1"/> <createData entity="SimpleMsiProduct" stepKey="simpleProduct"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdmin1"/> - <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="adminCreateNewWebsite"> <argument name="newWebsiteName" value="{{NewWebSiteData.name}}"/> <argument name="websiteCode" value="{{NewWebSiteData.code}}"/> @@ -70,6 +68,10 @@ <actionGroup ref="DisableSourceActionGroup" stepKey="disableCreatedSource"> <argument name="sourceCode" value="$$additionalSource.source[source_code]$$"/> </actionGroup> + <!--Delete customer created during test execution--> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomerFromGrid"> + <argument name="customerEmail" value="Simple_US_Customer.email"/> + </actionGroup> <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCategory1" stepKey="deleteCategory"/> <magentoCLI command="config:set {{StorefrontDisableAddStoreCodeToUrls.path}} {{StorefrontDisableAddStoreCodeToUrls.value}}" stepKey="addStoreCodeToUrlDisable"/> @@ -80,38 +82,31 @@ <argument name="tags" value=""/> </actionGroup> </after> - <!-- Assign product to custom website --> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="openProductGrid"/> <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProduct1"> <argument name="product" value="$$simpleProduct$$"/> </actionGroup> - <actionGroup ref="OpenProductForEditByClickingRowXColumnYInProductGridActionGroup" stepKey="openFirstProductForEdit"/> <actionGroup ref="AssignSourceToProductActionGroup" stepKey="assignTestSourceToCreatedProduct" > <argument name="sourceCode" value="$$additionalSource.source[source_code]$$"/> </actionGroup> - <actionGroup ref="AdminFillSourceQtyOnProductEditPageActionGroup" stepKey="fillProductQtyPerSource"> <argument name="sourceCode" value="$$additionalSource.source[source_code]$$"/> <argument name="qty" value="20" /> </actionGroup> - <actionGroup ref="AdminChangeManageStockStatusOnProductEditPageActionGroup" stepKey="disableMangeStockOnProductEditPage"> <argument name="manageStock" value="No"/> </actionGroup> - <actionGroup ref="AddWebsiteToProductActionGroup" stepKey="assignProductToAdditionalWebsite" > <argument name="website" value="{{NewWebSiteData.name}}"/> </actionGroup> - <actionGroup ref="AdminNavigateToNewOrderPageExistingCustomerActionGroup" stepKey="createNewOrderForCustomer"> <argument name="customer" value="Simple_US_Customer"/> </actionGroup> <actionGroup ref="AdminSelectStoreDuringOrderCreationActionGroup" stepKey="selectCustomStore"> <argument name="storeView" value="customStore"/> </actionGroup> - <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addSimpleProductToTheOrder"> <argument name="product" value="$$simpleProduct$$"/> </actionGroup> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminUserCreateOrderWithSimpleProductOnTestStockFromCustomWebsiteTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminUserCreateOrderWithSimpleProductOnTestStockFromCustomWebsiteTest.xml index c1a14f961a2..36862854c09 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminUserCreateOrderWithSimpleProductOnTestStockFromCustomWebsiteTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminUserCreateOrderWithSimpleProductOnTestStockFromCustomWebsiteTest.xml @@ -79,7 +79,10 @@ <!--Delete created website, store and store view --> <actionGroup ref="AdminSystemStoreOpenPageActionGroup" stepKey="navigateToStores"/> <actionGroup ref="AdminDeleteMultipleWebsitesActionGroup" stepKey="deleteWebsite" /> - + <!--Delete customer created during test execution--> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomerFromGrid"> + <argument name="customerEmail" value="Msi_US_Customer.email"/> + </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableAllSources"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> @@ -87,7 +90,6 @@ <argument name="indices" value=""/> </actionGroup> </after> - <!--- Place order. --> <actionGroup ref="AdminNavigateToNewOrderPageNewCustomerActionGroup" stepKey="createNewOrderForCustomer"/> <actionGroup ref="AdminSelectStoreDuringOrderCreationActionGroup" stepKey="selectCustomStore"> diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminUserSetStatusForEachSourceItemTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminUserSetStatusForEachSourceItemTest.xml index 5c6c2754e57..31aae213438 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminUserSetStatusForEachSourceItemTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminUserSetStatusForEachSourceItemTest.xml @@ -57,6 +57,7 @@ <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct"/> <deleteData createDataKey="simpleCategory1" stepKey="deleteCategory1"/> </after> diff --git a/InventoryAdminUi/Test/Mftf/Test/CatalogPriceRuleConfigurableProductAdditionalStockTest.xml b/InventoryAdminUi/Test/Mftf/Test/CatalogPriceRuleConfigurableProductAdditionalStockTest.xml index cd7ab9c0933..57e5b996f36 100644 --- a/InventoryAdminUi/Test/Mftf/Test/CatalogPriceRuleConfigurableProductAdditionalStockTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/CatalogPriceRuleConfigurableProductAdditionalStockTest.xml @@ -70,6 +70,7 @@ <actionGroup ref="DisableSourceActionGroup" stepKey="disableCreatedSource" before="assignMainWebsiteToDefaultStock"> <argument name="sourceCode" value="$$additionalSource.source[source_code]$$"/> </actionGroup> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <!--Delete stock.--> <deleteData createDataKey="additionalStock" stepKey="deleteStock" after="assignMainWebsiteToDefaultStock"/> </after> diff --git a/InventoryAdminUi/Test/Mftf/Test/RegisteredCustomerCreateOrderWithSimpleProductAdditionalStockAdditionalWebsiteTest.xml b/InventoryAdminUi/Test/Mftf/Test/RegisteredCustomerCreateOrderWithSimpleProductAdditionalStockAdditionalWebsiteTest.xml index 56610fc18ca..bf943a75b8e 100644 --- a/InventoryAdminUi/Test/Mftf/Test/RegisteredCustomerCreateOrderWithSimpleProductAdditionalStockAdditionalWebsiteTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/RegisteredCustomerCreateOrderWithSimpleProductAdditionalStockAdditionalWebsiteTest.xml @@ -19,7 +19,14 @@ <group value="multi_mode"/> <group value="pr_exclude"/> </annotations> - + <after> + <!--Delete customer created during test execution, login to admin are again--> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminAreaAgain"/> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomerFromGrid"> + <argument name="customerEmail" value="Simple_US_Customer.email"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdminAgain"/> + </after> <!--Register customer.--> <actionGroup ref="RegisterCustomOnStorefrontActionGroup" stepKey="registerCustomer"> <argument name="Customer" value="Simple_US_Customer"/> diff --git a/InventoryAdminUi/Test/Mftf/Test/StorefrontCacheValidationConfigurableProductSoldOutInSingleStockModeTest.xml b/InventoryAdminUi/Test/Mftf/Test/StorefrontCacheValidationConfigurableProductSoldOutInSingleStockModeTest.xml index f0e2389ec6c..c519e96dffa 100644 --- a/InventoryAdminUi/Test/Mftf/Test/StorefrontCacheValidationConfigurableProductSoldOutInSingleStockModeTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/StorefrontCacheValidationConfigurableProductSoldOutInSingleStockModeTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="single_mode"/> </annotations> - <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableSources"/> @@ -57,12 +56,12 @@ <deleteData createDataKey="configurableChildProduct" stepKey="deleteConfigurableProductVariation"/> <deleteData createDataKey="configurableProductAttribute" stepKey="deleteConfigurableProductAttribute"/> <deleteData createDataKey="category" stepKey="deletecategory"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value=""/> </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdminArea"/> </after> - <!--Add configurable product to cart.--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$customer$" /> diff --git a/InventoryAdminUi/Test/Mftf/Test/StorefrontCreateOrderAllQuantityConfigurableProductCustomStockTest.xml b/InventoryAdminUi/Test/Mftf/Test/StorefrontCreateOrderAllQuantityConfigurableProductCustomStockTest.xml index ef53581358a..63c5b2c9508 100644 --- a/InventoryAdminUi/Test/Mftf/Test/StorefrontCreateOrderAllQuantityConfigurableProductCustomStockTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/StorefrontCreateOrderAllQuantityConfigurableProductCustomStockTest.xml @@ -17,7 +17,6 @@ <group value="multi_mode"/> <group value="pr_exclude"/> </annotations> - <before> <!--Create test data.--> <createData entity="SimpleSubCategory" stepKey="category"/> @@ -75,6 +74,7 @@ <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Disable source.--> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableAllSources"/> <deleteData createDataKey="category" stepKey="deletecategory"/> @@ -83,7 +83,6 @@ </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdminArea"/> </after> - <!--Add configurable product to cart.--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$customer$" /> diff --git a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderAllOptionQuantityConfigurableProductCustomStockTest.xml b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderAllOptionQuantityConfigurableProductCustomStockTest.xml index 585455c8c4d..9ab991d7e96 100644 --- a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderAllOptionQuantityConfigurableProductCustomStockTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderAllOptionQuantityConfigurableProductCustomStockTest.xml @@ -17,7 +17,6 @@ <group value="multi_mode"/> <group value="pr_exclude"/> </annotations> - <before> <!--Create test data.--> <createData entity="_defaultCategory" stepKey="category"/> @@ -103,6 +102,7 @@ <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Disable source.--> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableAllSources"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> @@ -111,7 +111,6 @@ <deleteData createDataKey="category" stepKey="deletecategory"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdminArea"/> </after> - <!--Add configurable product to cart.--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$$customer$$" /> @@ -132,7 +131,6 @@ <!-- Assert out of stock option is absent on product page --> <amOnPage url="{{StorefrontProductPage.url($$configurableProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToConfigurablePDP"/> <waitForPageLoad stepKey="waitForConfigurablePDP"/> - <grabMultiple selector="{{StorefrontProductInfoMainSection.selectableProductOptions($$configurableProductAttribute.attribute_id$$)}}" stepKey="productOptions"/> <assertEquals stepKey="assertProductOptions"> <actualResult type="variable">productOptions</actualResult> diff --git a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderWithSimpleProductOnTestStockFromCustomWebsiteTest.xml b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderWithSimpleProductOnTestStockFromCustomWebsiteTest.xml index 53a3e5a8afc..d7ea5ab351b 100644 --- a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderWithSimpleProductOnTestStockFromCustomWebsiteTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderWithSimpleProductOnTestStockFromCustomWebsiteTest.xml @@ -18,14 +18,12 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <createData entity="SimpleSubCategory" stepKey="simpleCategory"/> <!--Add store code to url.--> <magentoCLI command="config:set {{StorefrontEnableAddStoreCodeToUrls.path}} {{StorefrontEnableAddStoreCodeToUrls.value}}" stepKey="addStoreCodeToUrlEnable"/> - <!--Create additional source and stock.--> <createData entity="BasicMsiStock1" stepKey="additionalStock"/> <createData entity="FullSource1" stepKey="createSource"/> @@ -37,7 +35,6 @@ <createData entity="SimpleProduct" stepKey="simpleProduct"> <requiredEntity createDataKey="simpleCategory"/> </createData> - <!--Create new website,store and store view--> <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite" > <argument name="newWebsiteName" value="{{customWebsite.name}}"/> @@ -52,7 +49,6 @@ <argument name="StoreGroup" value="customStoreGroup"/> <argument name="customStore" value="customStore"/> </actionGroup> - <!--Assign Custom Stock to Additional Website.--> <actionGroup ref="AssignWebsiteToStockActionGroup" stepKey="assignStockToAdditionalWebsite"> <argument name="stockName" value="{{BasicMsiStock1.name}}"/> @@ -82,6 +78,8 @@ <actionGroup ref="DisableSourceActionGroup" stepKey="disableCreatedSource"> <argument name="sourceCode" value="$$createSource.source[source_code]$$"/> </actionGroup> + <!-- child test is generating same customer email, so we need to make sure that customer is deleted --> + <actionGroup ref="AdminDeleteAllCustomerActionGroup" stepKey="deleteAllCustomers"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin1"/> <magentoCLI command="config:set {{StorefrontDisableAddStoreCodeToUrls.path}} {{StorefrontDisableAddStoreCodeToUrls.value}}" @@ -90,7 +88,6 @@ <argument name="indices" value=""/> </actionGroup> </after> - <!-- Assign product to custom website --> <amOnPage url="{{AdminProductEditPage.url($$simpleProduct.id$$)}}" stepKey="openProductEditPage"/> <actionGroup ref="AssignSourceToProductActionGroup" stepKey="assignTestSourceToCreatedProduct" > @@ -100,18 +97,15 @@ <actionGroup ref="AddWebsiteToProductActionGroup" stepKey="assignProductToAdditionalWebsite" > <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> - <!-- Create a user account --> <actionGroup ref="RegisterCustomOnStorefrontActionGroup" stepKey="createAnAccount"> <argument name="Customer" value="MsiCustomer1"/> <argument name="storeCode" value="customStore.code"/> </actionGroup> - <actionGroup ref="StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup" stepKey="openProductPageUsingStoreCodeInUrl"> <argument name="product" value="$$simpleProduct$$"/> <argument name="storeView" value="customStore"/> </actionGroup> - <!--Place order --> <fillField selector="{{StorefrontProductPageSection.qtyInput}}" userInput="5" stepKey="fillCorrectQuantity"/> <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addProductToCart"/> @@ -127,7 +121,6 @@ <comment userInput="BIC workaround" stepKey="waitUntilOrderPlaced"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> <see selector="{{CheckoutSuccessMainSection.success}}" userInput="Your order number is:" stepKey="checkOrderPlaceSuccessMessage"/> - <!--Verify that reservation '-5' items of 'Configurable product 1-red' on Default stock is created correctly --> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndexPageForCheckProductQtyAfterReorder"/> diff --git a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml index ea8627fe076..f70ad56dc74 100644 --- a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderConfigurableProductWithDropDownAttributeAndCustomSourceTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <!--Create test data.--> <createData entity="SimpleSubCategory" stepKey="category"/> @@ -76,6 +75,7 @@ <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Disable source.--> <actionGroup ref="DisableSourceActionGroup" stepKey="disableSource"> <argument name="sourceCode" value="$source.source[source_code]$"/> @@ -88,7 +88,6 @@ <argument name="indices" value=""/> </actionGroup> </after> - <!--Add configurable product to cart.--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$customer$" /> diff --git a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderConfigurableProductWithDropDownAttributeAndDefaultSourceTest.xml b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderConfigurableProductWithDropDownAttributeAndDefaultSourceTest.xml index 035c1d5e573..5a4acb4e16c 100644 --- a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderConfigurableProductWithDropDownAttributeAndDefaultSourceTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderConfigurableProductWithDropDownAttributeAndDefaultSourceTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <!--Create test data.--> <createData entity="SimpleSubCategory" stepKey="category"/> @@ -65,6 +64,7 @@ <deleteData createDataKey="configurableProduct" stepKey="deleteConfigurableProduct"/> <deleteData createDataKey="configurableChildProduct" stepKey="deleteConfigurableProductVariation"/> <deleteData createDataKey="configurableProductAttribute" stepKey="deleteConfigurableProductAttribute"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Disable source.--> <actionGroup ref="DisableSourceActionGroup" stepKey="disableSource"> <argument name="sourceCode" value="$source.source[source_code]$"/> @@ -77,7 +77,6 @@ <argument name="indices" value=""/> </actionGroup> </after> - <!--Add configurable product to cart.--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$customer$" /> diff --git a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderedVirtualProductOnDefaultStockTest.xml b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderedVirtualProductOnDefaultStockTest.xml index 9e893db8b2d..4cd398e60f9 100644 --- a/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderedVirtualProductOnDefaultStockTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/StorefrontLoggedInCustomerOrderedVirtualProductOnDefaultStockTest.xml @@ -18,16 +18,13 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <createData entity="SimpleSubCategory" stepKey="category"/> <createData entity="_minimalSource" stepKey="customSource"/> <createData entity="BasicMsiStock1" stepKey="customStock"/> <createData entity="Msi_US_Customer" stepKey="customer"/> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminArea"/> <waitForPageLoad stepKey="waitForDashboardLoad"/> - <comment userInput="Assign Default Stock to Main Website " stepKey="assignDefaultStockToMainWebsiteComment"/> <amOnPage url="{{AdminManageStockPage.url}}" stepKey="navigateToStockListPageToAssignDefaultStockToMainWebsite"/> <waitForPageLoad time="30" stepKey="waitForStockListPageLoad"/> @@ -38,7 +35,6 @@ <waitForPageLoad time="30" stepKey="waitFroDefaultStockEditPageLoad"/> <selectOption selector="{{AdminEditStockSalesChannelsSection.websites}}" userInput="Main Website" stepKey="selectDefaultWebsiteAsSalesChannelForDefaultStock"/> <click selector="{{AdminGridMainControls.saveAndContinue}}" stepKey="saveDefaultStock"/> - <comment userInput="Assign source to stock." stepKey="assignSourceToStockComment"/> <amOnPage url="{{AdminManageStockPage.url}}" stepKey="navigateToStockListPageToAssignCustomSourceToCustomStock"/> <waitForPageLoad time="30" stepKey="waitForStockGridLoad"/> @@ -54,16 +50,14 @@ <click selector="{{AdminGridRow.checkboxByValue($$customSource.source[name]$$)}}" stepKey="selectCustomSourceForCustomStock"/> <click selector="{{AdminManageSourcesGridControls.done}}" stepKey="clickOnDoneCustomSourceAssignment"/> <click selector="{{AdminGridMainControls.saveAndContinue}}" stepKey="saveCustomStock"/> - <createData entity="VirtualProduct" stepKey="product"/> </before> <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdminArea"/> - <deleteData createDataKey="category" stepKey="deleteCategory"/> <deleteData createDataKey="product" stepKey="deleteProduct"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> </after> - <comment userInput="Assign category to created virtual product." stepKey="assignCategoryToProductComment"/> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndexPageForEditProduct"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="findVirtualProductBySku"> @@ -73,16 +67,12 @@ <click selector="{{AdminGridColumnsControls.columns}}" stepKey="selectColumns"/> <click selector="{{AdminGridColumnsControls.reset}}" stepKey="clickOnResetToRestoreDefaultColumns"/> <click selector="{{AdminProductGridSection.productGridXRowYColumnButton('1', '2')}}" stepKey="openProductEditPage"/> - <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$category.name$$]" requiredAction="true" stepKey="searchAndSelectCategory"/> - <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="saveAndCloseCreatedVirtualProduct"/> - <comment userInput="Login and buy fifty pieces of virtual product on storefront." stepKey="buyVirtualProductComment"/> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefront"> <argument name="Customer" value="$$customer$$"/> </actionGroup> - <amOnPage url="{{StorefrontCategoryPage.url($$category.custom_attributes[url_key]$$)}}" stepKey="navigateToCategoryPage"/> <actionGroup ref="StorefrontAddCategoryProductToCartWithQuantityActionGroup" stepKey="addVirtualProductToCart"> <argument name="product" value="$$product$$"/> @@ -98,14 +88,12 @@ <comment userInput="BIC workaround" stepKey="waitUntilOrderPlaced"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> <see selector="{{CheckoutSuccessMainSection.success}}" userInput="Your order number is:" stepKey="checkOrderPlaceSuccessMessage"/> - <comment userInput="Back to admin area, to check ordered quantity" stepKey="checkOrderedQuantity"/> <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> <argument name="orderId" value="{$grabOrderNumber}"/> </actionGroup> <waitForElementVisible selector="{{AdminOrderItemsOrderedSection.itemQty('1')}}" stepKey="waitForViewOrderedQuantity"/> <see selector="{{AdminOrderItemsOrderedSection.itemQty('1')}}" userInput="Ordered 50" stepKey="orderedQuantity"/> - <comment userInput="Check product sources quantity and salable quantity after order placed." stepKey="checkProductQtyAfterPlaceOrderComment"/> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndexPageForCheckProductQtyAfterPlaceOrder"/> <actionGroup ref="AdminGridFilterSearchResultsByInput" stepKey="findVirtualProductBySkuToCheckQtyAfterPlaceOrder"> diff --git a/InventoryAdminUi/Test/Mftf/Test/ValidateAfterPlacingAnOrderWithZeroStatusAsOrderedPartialAndBackOrderedAccordinglyTest.xml b/InventoryAdminUi/Test/Mftf/Test/ValidateAfterPlacingAnOrderWithZeroStatusAsOrderedPartialAndBackOrderedAccordinglyTest.xml index 737f1f577bf..d56caf73fa5 100644 --- a/InventoryAdminUi/Test/Mftf/Test/ValidateAfterPlacingAnOrderWithZeroStatusAsOrderedPartialAndBackOrderedAccordinglyTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/ValidateAfterPlacingAnOrderWithZeroStatusAsOrderedPartialAndBackOrderedAccordinglyTest.xml @@ -18,7 +18,6 @@ <group value="msi"/> <group value="multi_mode"/> </annotations> - <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <!-- Create category, product and customer--> @@ -27,7 +26,6 @@ <requiredEntity createDataKey="category"/> </createData> <createData entity="MsiCustomer1" stepKey="customer"/> - <!-- Create source and stock--> <createData entity="FullSource1" stepKey="source"/> <createData entity="BasicMsiStockWithMainWebsite1" stepKey="stock"/> @@ -35,7 +33,6 @@ <requiredEntity createDataKey="stock"/> <requiredEntity createDataKey="source"/> </createData> - <!--Create 3 product.--> <createData entity="_defaultProduct" stepKey="product1"> <requiredEntity createDataKey="category"/> @@ -46,11 +43,9 @@ <createData entity="SimpleProduct3" stepKey="product3"> <requiredEntity createDataKey="category"/> </createData> - <!--Create Bundle product --> <createData entity="ApiBundleProductShipmentTypeSeparately" stepKey="createBundleProduct"/> </before> - <!-- select allow Backorder below 0 and notify customer--> <comment userInput="Fill config 'Backorders'." stepKey="fillConfigComment"/> <magentoCLI command="config:set cataloginventory/item_options/backorders 2" stepKey="fillBackordersConfigValue"/> @@ -61,7 +56,6 @@ <actionGroup ref="CliCacheFlushActionGroup" stepKey="cleanCacheAfterFillBackordersConfig"> <argument name="tags" value=""/> </actionGroup> - <!--Assign source to product, and save the product--> <amOnPage url="{{AdminProductEditPage.url($$product.id$$)}}" stepKey="openProductEditPage"/> <waitForPageLoad stepKey="waitForPageToLoad"/> @@ -71,18 +65,15 @@ <argument name="sourceQty" value="0"/> </actionGroup> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> - <!-- Login as customer --> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefront"> <argument name="Customer" value="$$customer$$"/> </actionGroup> - <!-- Add product to shopping cart --> <amOnPage url="{{StorefrontProductPage.url($product.custom_attributes[url_key]$)}}" stepKey="navigateToToPDP"/> <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToCart"> <argument name="productQty" value="1"/> </actionGroup> - <!--Place order.--> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="navigateToCheckout"/> <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickOnNextPaymentPage"/> @@ -90,7 +81,6 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLink"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId"/> - <!--Check placed order in admin grid and validate the product.--> <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrderGrid"> <argument name="entityId" value="{$orderId}"/> @@ -99,7 +89,6 @@ <argument name="product_name" value="$$product.name$$"/> <argument name="status" value="Backordered"/> </actionGroup> - <!--Assign source to all 3 product, and save the product--> <amOnPage url="{{AdminProductEditPage.url($$product1.id$$)}}" stepKey="openProduct1EditPage"/> <waitForPageLoad stepKey="waitForPageToLoad1"/> @@ -125,12 +114,10 @@ <argument name="sourceQty" value="2"/> </actionGroup> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct3"/> - <!--Search for the Created Bundle product --> <actionGroup ref="FilterAndSelectProductActionGroup" stepKey="openBundleProductPage"> <argument name="productSku" value="$$createBundleProduct.sku$$"/> </actionGroup> - <!--Update the Created Bundle product--> <actionGroup ref="AdminAddOptionToBundleProductActionGroup" stepKey="AdminAddOptionToBundleProduct"> <argument name="categoryName" value="$$category.name$$"/> @@ -147,23 +134,18 @@ <click selector="{{AdminAddProductsToOptionPanel.addSelectedProducts}}" stepKey="clickAddSelectedBundleProducts"/> <fillField selector="{{AdminProductFormBundleSection.defaultQuantity1}}" userInput="1" stepKey="fillQtyForSimpleProductAdded"/> <scrollToTopOfPage stepKey="scrollToTheTopOfBundleProductPage"/> - <!--Save the Bundle Product--> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveBundleProduct"/> - <!-- Verify the Bundle Product in Created Category in Storefront--> <actionGroup ref="AssertProductInStorefrontCategoryPage" stepKey="assertProductInStorefront1"> <argument name="category" value="$$category$$"/> <argument name="product" value="$$createBundleProduct$$"/> </actionGroup> - <!-- Add product to shopping cart --> <amOnPage url="{{StorefrontProductPage.url($createBundleProduct.custom_attributes[url_key]$)}}" stepKey="navigateToToPDP1"/> <comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="waitForBundleProductPageToLoad"/> - <!--Storefront: Verify Product option details--> <actionGroup ref="StorefrontAddMultipleSimpleProductToBundleProductActionGroup" stepKey="addBundleProductInStorefront"/> - <!--Place order Again.--> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="navigateToCheckoutAgain"/> <actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickOnNextPaymentPageAgain"/> @@ -171,7 +153,6 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrderAgain"/> <actionGroup ref="StorefrontClickOrderLinkFromCheckoutSuccessPageActionGroup" stepKey="clickOrderLinkAgain"/> <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="orderId1"/> - <!--Check placed order in admin grid and validate the all product status .--> <actionGroup ref="AdminOpenOrderByEntityIdActionGroup" stepKey="openOrderInGrid"> <argument name="entityId" value="{$orderId1}"/> @@ -188,7 +169,6 @@ <argument name="product_name" value="$$product3.name$$"/> <argument name="status" value="Ordered"/> </actionGroup> - <after> <comment userInput="Fill config 'Backorders'." stepKey="fillConfigComment1"/> <magentoCLI command="config:set cataloginventory/item_options/backorders 0" stepKey="fillDefaultBackordersConfigValue1"/> @@ -199,7 +179,6 @@ <actionGroup ref="CliCacheFlushActionGroup" stepKey="cleanCacheAfterFillBackordersConfig1"> <argument name="tags" value=""/> </actionGroup> - <!--Delete the created products and catagory.--> <deleteData createDataKey="product" stepKey="deleteProduct"/> <deleteData createDataKey="product1" stepKey="deleteProduct1"/> @@ -207,14 +186,13 @@ <deleteData createDataKey="product3" stepKey="deleteProduct3"/> <deleteData createDataKey="createBundleProduct" stepKey="deleteProduct4"/> <deleteData createDataKey="category" stepKey="deleteCategory"/> - <!--Assign Default Stock to Default Website.--> <actionGroup ref="AssignWebsiteToStockActionGroup" stepKey="assignMainWebsiteToDefaultStock"> <argument name="stockName" value="{{_defaultStock.name}}"/> <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> <deleteData createDataKey="stock" stepKey="deleteCustomStock"/> - + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Disable source.--> <actionGroup ref="DisableSourceActionGroup" stepKey="deleteSource"> <argument name="sourceCode" value="$source.source[source_code]$"/> diff --git a/InventoryBundleProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php b/InventoryBundleProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php new file mode 100644 index 00000000000..9d37cec51a5 --- /dev/null +++ b/InventoryBundleProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryBundleProduct\Test\Integration\Model; + +use Magento\Bundle\Model\Product\Type as BundleType; +use Magento\InventoryCatalogApi\Model\CompositeProductTypesProviderInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +class CompositeProductTypesProviderTest extends TestCase +{ + public function testExecute(): void + { + $compositeProductTypesProvider = Bootstrap::getObjectManager() + ->create(CompositeProductTypesProviderInterface::class); + $compositeProductTypes = $compositeProductTypesProvider->execute(); + self::assertContains(BundleType::TYPE_CODE, $compositeProductTypes); + } +} diff --git a/InventoryBundleProductIndexer/Indexer/OptionsStatusSelectBuilder.php b/InventoryBundleProductIndexer/Indexer/OptionsStatusSelectBuilder.php index dcd61dad236..5ca733dfaab 100644 --- a/InventoryBundleProductIndexer/Indexer/OptionsStatusSelectBuilder.php +++ b/InventoryBundleProductIndexer/Indexer/OptionsStatusSelectBuilder.php @@ -13,67 +13,45 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryConfigurationApi\Model\InventoryConfigurationInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; +use Magento\InventoryIndexer\Indexer\InventoryIndexer; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameResolverInterface; class OptionsStatusSelectBuilder { - /** - * @var ResourceConnection - */ - private $resourceConnection; - - /** - * @var IndexNameResolverInterface - */ - private $indexNameResolver; - - /** - * @var MetadataPool - */ - private $metadataPool; - - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - - /** - * @var InventoryConfigurationInterface - */ - private $inventoryConfiguration; - /** * @param ResourceConnection $resourceConnection + * @param IndexNameBuilder $indexNameBuilder * @param IndexNameResolverInterface $indexNameResolver * @param MetadataPool $metadataPool * @param DefaultStockProviderInterface $defaultStockProvider * @param InventoryConfigurationInterface $inventoryConfiguration */ public function __construct( - ResourceConnection $resourceConnection, - IndexNameResolverInterface $indexNameResolver, - MetadataPool $metadataPool, - DefaultStockProviderInterface $defaultStockProvider, - InventoryConfigurationInterface $inventoryConfiguration + private readonly ResourceConnection $resourceConnection, + private readonly IndexNameBuilder $indexNameBuilder, + private readonly IndexNameResolverInterface $indexNameResolver, + private readonly MetadataPool $metadataPool, + private readonly DefaultStockProviderInterface $defaultStockProvider, + private readonly InventoryConfigurationInterface $inventoryConfiguration, ) { - $this->resourceConnection = $resourceConnection; - $this->indexNameResolver = $indexNameResolver; - $this->metadataPool = $metadataPool; - $this->defaultStockProvider = $defaultStockProvider; - $this->inventoryConfiguration = $inventoryConfiguration; } /** * Build bundle options stock status select * - * @param IndexName $indexName - * @param array $skuList + * @param int $stockId + * @param string[] $skuList + * @param IndexAlias $indexAlias * @return Select */ - public function execute(IndexName $indexName, array $skuList = []): Select + public function execute(int $stockId, array $skuList = [], IndexAlias $indexAlias = IndexAlias::MAIN): Select { + $indexName = $this->indexNameBuilder->setIndexId(InventoryIndexer::INDEXER_ID) + ->addDimension('stock_', (string) $stockId) + ->setAlias($indexAlias->value) + ->build(); $indexTableName = $this->indexNameResolver->resolveName($indexName); $metadata = $this->metadataPool->getMetadata(ProductInterface::class); $productLinkField = $metadata->getLinkField(); diff --git a/InventoryBundleProductIndexer/Indexer/SelectBuilder.php b/InventoryBundleProductIndexer/Indexer/SelectBuilder.php index 17914d861e4..539b4f15a00 100644 --- a/InventoryBundleProductIndexer/Indexer/SelectBuilder.php +++ b/InventoryBundleProductIndexer/Indexer/SelectBuilder.php @@ -13,7 +13,7 @@ use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\IndexStructure; use Magento\InventoryIndexer\Indexer\SiblingSelectBuilderInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; /** * Get bundle product for given stock select builder. @@ -53,11 +53,11 @@ public function __construct( /** * @inheritdoc */ - public function getSelect(IndexName $indexName, array $skuList = []): Select + public function getSelect(int $stockId, array $skuList = [], IndexAlias $indexAlias = IndexAlias::MAIN): Select { $connection = $this->resourceConnection->getConnection(); - $optionsStatusSelect = $this->optionsStatusSelectBuilder->execute($indexName, $skuList); + $optionsStatusSelect = $this->optionsStatusSelectBuilder->execute($stockId, $skuList, $indexAlias); $isRequiredOptionUnavailable = $connection->getCheckSql( 'options.required AND options.stock_status = 0', '1', diff --git a/InventoryBundleProductIndexer/Indexer/SiblingProductsProvider.php b/InventoryBundleProductIndexer/Indexer/SiblingProductsProvider.php index 09bca18fdbc..2c57c684be7 100644 --- a/InventoryBundleProductIndexer/Indexer/SiblingProductsProvider.php +++ b/InventoryBundleProductIndexer/Indexer/SiblingProductsProvider.php @@ -11,20 +11,16 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\EntityManager\MetadataPool; use Magento\InventoryIndexer\Indexer\SiblingProductsProviderInterface; -use Magento\InventoryIndexer\Indexer\SiblingSelectBuilderInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; class SiblingProductsProvider implements SiblingProductsProviderInterface { /** * @param ResourceConnection $resourceConnection * @param MetadataPool $metadataPool - * @param SiblingSelectBuilderInterface $selectBuilder */ public function __construct( private readonly ResourceConnection $resourceConnection, private readonly MetadataPool $metadataPool, - private readonly SiblingSelectBuilderInterface $selectBuilder, ) { } @@ -56,16 +52,4 @@ public function getSkus(array $skus): array return $siblingSkus; } - - /** - * @inheritdoc - */ - public function getData(IndexName $indexName, array $skuList = []): array - { - $connection = $this->resourceConnection->getConnection(); - $select = $this->selectBuilder->getSelect($indexName, $skuList); - $data = $connection->fetchAll($select); - - return $data; - } } diff --git a/InventoryBundleProductIndexer/Indexer/StockIndexer.php b/InventoryBundleProductIndexer/Indexer/StockIndexer.php deleted file mode 100644 index 543ee1274e0..00000000000 --- a/InventoryBundleProductIndexer/Indexer/StockIndexer.php +++ /dev/null @@ -1,141 +0,0 @@ -<?php -/** - * Copyright 2020 Adobe - * All Rights Reserved. - */ -declare(strict_types=1); - -namespace Magento\InventoryBundleProductIndexer\Indexer; - -use ArrayIterator; -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Exception\StateException; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; -use Magento\InventoryIndexer\Indexer\InventoryIndexer; -use Magento\InventoryIndexer\Indexer\SiblingProductsProviderInterface; -use Magento\InventoryIndexer\Indexer\Stock\GetAllStockIds; -use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexHandlerInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexStructureInterface; - -/** - * Index bundle products for given stocks. - */ -class StockIndexer -{ - /** - * @var GetAllStockIds - */ - private $getAllStockIds; - - /** - * @var IndexStructureInterface - */ - private $indexStructure; - - /** - * @var IndexHandlerInterface - */ - private $indexHandler; - - /** - * @var IndexNameBuilder - */ - private $indexNameBuilder; - - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - - /** - * $indexStructure is reserved name for construct variable in index internal mechanism. - * - * @param GetAllStockIds $getAllStockIds - * @param IndexStructureInterface $indexStructure - * @param IndexHandlerInterface $indexHandler - * @param IndexNameBuilder $indexNameBuilder - * @param DefaultStockProviderInterface $defaultStockProvider - * @param SiblingProductsProviderInterface $productsProvider - */ - public function __construct( - GetAllStockIds $getAllStockIds, - IndexStructureInterface $indexStructure, - IndexHandlerInterface $indexHandler, - IndexNameBuilder $indexNameBuilder, - DefaultStockProviderInterface $defaultStockProvider, - private readonly SiblingProductsProviderInterface $productsProvider, - ) { - $this->getAllStockIds = $getAllStockIds; - $this->indexStructure = $indexStructure; - $this->indexHandler = $indexHandler; - $this->indexNameBuilder = $indexNameBuilder; - $this->defaultStockProvider = $defaultStockProvider; - } - - /** - * Index bundle products for all stocks. - * - * @return void - * @throws StateException - */ - public function executeFull() - { - $stockIds = $this->getAllStockIds->execute(); - $this->executeList($stockIds); - } - - /** - * Index bundle products for given stock. - * - * @param int $stockId - * @param array $skuList - * @return void - * @throws StateException - */ - public function executeRow(int $stockId, array $skuList = []) - { - $this->executeList([$stockId], $skuList); - } - - /** - * Index bundle products for given stocks. - * - * @param array $stockIds - * @param array $skuList - * @return void - * @throws StateException - */ - public function executeList(array $stockIds, array $skuList = []) - { - foreach ($stockIds as $stockId) { - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } - - $mainIndexName = $this->indexNameBuilder - ->setIndexId(InventoryIndexer::INDEXER_ID) - ->addDimension('stock_', (string)$stockId) - ->setAlias(Alias::ALIAS_MAIN) - ->build(); - - if (!$this->indexStructure->isExist($mainIndexName, ResourceConnection::DEFAULT_CONNECTION)) { - $this->indexStructure->create($mainIndexName, ResourceConnection::DEFAULT_CONNECTION); - } - - $data = $this->productsProvider->getData($mainIndexName, $skuList); - $this->indexHandler->cleanIndex( - $mainIndexName, - new ArrayIterator($skuList), - ResourceConnection::DEFAULT_CONNECTION - ); - - $this->indexHandler->saveIndex( - $mainIndexName, - new ArrayIterator($data), - ResourceConnection::DEFAULT_CONNECTION - ); - } - } -} diff --git a/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterAddBundleSelectionPlugin.php b/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterAddBundleSelectionPlugin.php index 340acd38c30..7fb517e7a05 100644 --- a/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterAddBundleSelectionPlugin.php +++ b/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterAddBundleSelectionPlugin.php @@ -11,7 +11,8 @@ use Magento\Bundle\Api\ProductLinkManagementInterface; use Magento\Catalog\Api\Data\ProductInterface; use Magento\InventoryApi\Model\GetStockIdsBySkusInterface; -use Magento\InventoryBundleProductIndexer\Indexer\StockIndexer; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; use Psr\Log\LoggerInterface; /** @@ -19,34 +20,18 @@ */ class ReindexSourceItemsAfterAddBundleSelectionPlugin { - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var GetStockIdsBySkusInterface - */ - private $getStockIdsBySkus; - - /** - * @var StockIndexer - */ - private $stockIndexer; - /** * @param LoggerInterface $logger * @param GetStockIdsBySkusInterface $getStockIdsBySkus - * @param StockIndexer $stockIndexer + * @param SkuListInStockFactory $skuListInStockFactory + * @param SkuListsProcessor $skuListsProcessor */ public function __construct( - LoggerInterface $logger, - GetStockIdsBySkusInterface $getStockIdsBySkus, - StockIndexer $stockIndexer + private readonly LoggerInterface $logger, + private readonly GetStockIdsBySkusInterface $getStockIdsBySkus, + private readonly SkuListInStockFactory $skuListInStockFactory, + private readonly SkuListsProcessor $skuListsProcessor, ) { - $this->logger = $logger; - $this->getStockIdsBySkus = $getStockIdsBySkus; - $this->stockIndexer = $stockIndexer; } /** @@ -69,7 +54,13 @@ public function afterAddChild( ): int { try { $stockIds = $this->getStockIdsBySkus->execute([$linkedProduct->getSku()]); - $this->stockIndexer->executeList($stockIds, [$product->getSku()]); + $skuListInStockList = []; + foreach ($stockIds as $stockId) { + $skuListInStockList[] = $this->skuListInStockFactory->create( + ['stockId' => $stockId, 'skuList' => [$product->getSku()]] + ); + } + $this->skuListsProcessor->reindexList($skuListInStockList); } catch (\Exception $e) { $this->logger->error($e->getMessage()); } diff --git a/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterBulkAddBundleSelectionPlugin.php b/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterBulkAddBundleSelectionPlugin.php index ab007a7d732..d28ccc12db7 100644 --- a/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterBulkAddBundleSelectionPlugin.php +++ b/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterBulkAddBundleSelectionPlugin.php @@ -11,7 +11,8 @@ use Magento\Bundle\Api\ProductLinkManagementAddChildrenInterface; use Magento\Catalog\Api\Data\ProductInterface; use Magento\InventoryApi\Model\GetStockIdsBySkusInterface; -use Magento\InventoryBundleProductIndexer\Indexer\StockIndexer; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; use Psr\Log\LoggerInterface; /** @@ -19,34 +20,18 @@ */ class ReindexSourceItemsAfterBulkAddBundleSelectionPlugin { - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var GetStockIdsBySkusInterface - */ - private $getStockIdsBySkus; - - /** - * @var StockIndexer - */ - private $stockIndexer; - /** * @param LoggerInterface $logger * @param GetStockIdsBySkusInterface $getStockIdsBySkus - * @param StockIndexer $stockIndexer + * @param SkuListInStockFactory $skuListInStockFactory + * @param SkuListsProcessor $skuListsProcessor */ public function __construct( - LoggerInterface $logger, - GetStockIdsBySkusInterface $getStockIdsBySkus, - StockIndexer $stockIndexer + private readonly LoggerInterface $logger, + private readonly GetStockIdsBySkusInterface $getStockIdsBySkus, + private readonly SkuListInStockFactory $skuListInStockFactory, + private readonly SkuListsProcessor $skuListsProcessor, ) { - $this->logger = $logger; - $this->getStockIdsBySkus = $getStockIdsBySkus; - $this->stockIndexer = $stockIndexer; } /** @@ -70,7 +55,13 @@ public function afterAddChildren( try { $skus = array_map(fn ($linkedProduct) => $linkedProduct->getSku(), $linkedProducts); $stockIds = $this->getStockIdsBySkus->execute($skus); - $this->stockIndexer->executeList($stockIds, [$product->getSku()]); + $skuListInStockList = []; + foreach ($stockIds as $stockId) { + $skuListInStockList[] = $this->skuListInStockFactory->create( + ['stockId' => $stockId, 'skuList' => $skus] + ); + } + $this->skuListsProcessor->reindexList($skuListInStockList); } catch (\Exception $e) { $this->logger->error($e->getMessage()); } diff --git a/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterRemoveBundleSelectionPlugin.php b/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterRemoveBundleSelectionPlugin.php index 8ca9b7db77c..43eb3685f61 100644 --- a/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterRemoveBundleSelectionPlugin.php +++ b/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterRemoveBundleSelectionPlugin.php @@ -9,7 +9,8 @@ use Magento\Bundle\Api\ProductLinkManagementInterface; use Magento\InventoryApi\Model\GetStockIdsBySkusInterface; -use Magento\InventoryBundleProductIndexer\Indexer\StockIndexer; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; use Psr\Log\LoggerInterface; /** @@ -17,34 +18,18 @@ */ class ReindexSourceItemsAfterRemoveBundleSelectionPlugin { - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var GetStockIdsBySkusInterface - */ - private $getStockIdsBySkus; - - /** - * @var StockIndexer - */ - private $stockIndexer; - /** * @param LoggerInterface $logger * @param GetStockIdsBySkusInterface $getStockIdsBySkus - * @param StockIndexer $stockIndexer + * @param SkuListInStockFactory $skuListInStockFactory + * @param SkuListsProcessor $skuListsProcessor */ public function __construct( - LoggerInterface $logger, - GetStockIdsBySkusInterface $getStockIdsBySkus, - StockIndexer $stockIndexer + private readonly LoggerInterface $logger, + private readonly GetStockIdsBySkusInterface $getStockIdsBySkus, + private readonly SkuListInStockFactory $skuListInStockFactory, + private readonly SkuListsProcessor $skuListsProcessor, ) { - $this->logger = $logger; - $this->getStockIdsBySkus = $getStockIdsBySkus; - $this->stockIndexer = $stockIndexer; } /** @@ -67,7 +52,13 @@ public function afterRemoveChild( ): bool { try { $stockIds = $this->getStockIdsBySkus->execute([$childSku]); - $this->stockIndexer->executeList($stockIds, [$sku]); + $skuListInStockList = []; + foreach ($stockIds as $stockId) { + $skuListInStockList[] = $this->skuListInStockFactory->create( + ['stockId' => $stockId, 'skuList' => [$sku]] + ); + } + $this->skuListsProcessor->reindexList($skuListInStockList); } catch (\Exception $e) { $this->logger->error($e->getMessage()); } diff --git a/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterSaveBundleSelectionPlugin.php b/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterSaveBundleSelectionPlugin.php index 01cee05380a..aa6ec38a11e 100644 --- a/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterSaveBundleSelectionPlugin.php +++ b/InventoryBundleProductIndexer/Plugin/Bundle/Model/LinkManagement/ReindexSourceItemsAfterSaveBundleSelectionPlugin.php @@ -10,7 +10,8 @@ use Magento\Bundle\Api\Data\LinkInterface; use Magento\Bundle\Api\ProductLinkManagementInterface; use Magento\InventoryApi\Model\GetStockIdsBySkusInterface; -use Magento\InventoryBundleProductIndexer\Indexer\StockIndexer; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; use Psr\Log\LoggerInterface; /** @@ -18,34 +19,18 @@ */ class ReindexSourceItemsAfterSaveBundleSelectionPlugin { - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var GetStockIdsBySkusInterface - */ - private $getStockIdsBySkus; - - /** - * @var StockIndexer - */ - private $stockIndexer; - /** * @param LoggerInterface $logger * @param GetStockIdsBySkusInterface $getStockIdsBySkus - * @param StockIndexer $stockIndexer + * @param SkuListInStockFactory $skuListInStockFactory + * @param SkuListsProcessor $skuListsProcessor */ public function __construct( - LoggerInterface $logger, - GetStockIdsBySkusInterface $getStockIdsBySkus, - StockIndexer $stockIndexer + private readonly LoggerInterface $logger, + private readonly GetStockIdsBySkusInterface $getStockIdsBySkus, + private readonly SkuListInStockFactory $skuListInStockFactory, + private readonly SkuListsProcessor $skuListsProcessor, ) { - $this->logger = $logger; - $this->getStockIdsBySkus = $getStockIdsBySkus; - $this->stockIndexer = $stockIndexer; } /** @@ -66,7 +51,13 @@ public function afterSaveChild( ): bool { try { $stockIds = $this->getStockIdsBySkus->execute([$linkedProduct->getSku()]); - $this->stockIndexer->executeList($stockIds, [$sku]); + $skuListInStockList = []; + foreach ($stockIds as $stockId) { + $skuListInStockList[] = $this->skuListInStockFactory->create( + ['stockId' => $stockId, 'skuList' => [$sku]] + ); + } + $this->skuListsProcessor->reindexList($skuListInStockList); } catch (\Exception $e) { $this->logger->error($e->getMessage()); } diff --git a/InventoryBundleProductIndexer/Test/Integration/Indexer/StockIndexerTest.php b/InventoryBundleProductIndexer/Test/Integration/Indexer/StockIndexerTest.php index 3ee40c9d1e4..e89d8ae9344 100644 --- a/InventoryBundleProductIndexer/Test/Integration/Indexer/StockIndexerTest.php +++ b/InventoryBundleProductIndexer/Test/Integration/Indexer/StockIndexerTest.php @@ -18,7 +18,8 @@ use Magento\InventoryApi\Test\Fixture\SourceItems as SourceItemsFixture; use Magento\InventoryApi\Test\Fixture\Stock as StockFixture; use Magento\InventoryApi\Test\Fixture\StockSourceLinks as StockSourceLinksFixture; -use Magento\InventoryBundleProductIndexer\Indexer\StockIndexer; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; use Magento\InventoryIndexer\Model\ResourceModel\GetStockItemData; use Magento\InventorySalesApi\Model\GetStockItemDataInterface; use Magento\InventorySalesApi\Test\Fixture\StockSalesChannels as StockSalesChannelsFixture; @@ -37,9 +38,14 @@ class StockIndexerTest extends TestCase private $fixtures; /** - * @var StockIndexer + * @var SkuListsProcessor */ - private $stockIndexer; + private $skuListsProcessor; + + /** + * @var SkuListInStockFactory + */ + private $skuListInStockFactory; /** * @var GetSourceItemsBySkuInterface @@ -59,7 +65,8 @@ class StockIndexerTest extends TestCase protected function setUp(): void { $this->fixtures = DataFixtureStorageManager::getStorage(); - $this->stockIndexer = Bootstrap::getObjectManager()->create(StockIndexer::class); + $this->skuListsProcessor = Bootstrap::getObjectManager()->create(SkuListsProcessor::class); + $this->skuListInStockFactory = Bootstrap::getObjectManager()->get(SkuListInStockFactory::class); $this->getSourceItemsBySku = Bootstrap::getObjectManager()->get(GetSourceItemsBySkuInterface::class); $this->sourceItemsSave = Bootstrap::getObjectManager()->get(SourceItemsSaveInterface::class); $this->getStockItemData = Bootstrap::getObjectManager()->get(GetStockItemData::class); @@ -148,7 +155,11 @@ public function testExecuteList(array $newSourceData, bool $expectedStockStatus) $this->sourceItemsSave->execute($sourceItems); } - $this->stockIndexer->executeList([$stock->getStockId()], ['bundle1']); + $skuListInStock = $this->skuListInStockFactory->create( + ['stockId' => $stock->getStockId(), 'skuList' => ['bundle1']] + ); + $this->skuListsProcessor->reindexList([$skuListInStock]); + $bundleStockItem = $this->getStockItemData->execute('bundle1', $stock->getStockId()); self::assertEquals($expectedStockStatus, (bool) $bundleStockItem[GetStockItemDataInterface::IS_SALABLE]); } diff --git a/InventoryBundleProductIndexer/etc/di.xml b/InventoryBundleProductIndexer/etc/di.xml index 148d9862cfe..c7b42faaeca 100644 --- a/InventoryBundleProductIndexer/etc/di.xml +++ b/InventoryBundleProductIndexer/etc/di.xml @@ -6,11 +6,6 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="Magento\InventoryBundleProductIndexer\Indexer\StockIndexer"> - <arguments> - <argument name="productsProvider" xsi:type="object">Magento\InventoryBundleProductIndexer\Indexer\SiblingProductsProvider</argument> - </arguments> - </type> <type name="Magento\Bundle\Api\ProductLinkManagementInterface"> <plugin name="reindex_source_items_after_add_bundle_selection" type="Magento\InventoryBundleProductIndexer\Plugin\Bundle\Model\LinkManagement\ReindexSourceItemsAfterAddBundleSelectionPlugin"/> <plugin name="reindex_source_items_after_save_bundle_selection" type="Magento\InventoryBundleProductIndexer\Plugin\Bundle\Model\LinkManagement\ReindexSourceItemsAfterSaveBundleSelectionPlugin"/> @@ -19,12 +14,14 @@ <type name="Magento\Bundle\Api\ProductLinkManagementAddChildrenInterface"> <plugin name="reindex_source_items_after_bulk_add_bundle_selection" type="Magento\InventoryBundleProductIndexer\Plugin\Bundle\Model\LinkManagement\ReindexSourceItemsAfterBulkAddBundleSelectionPlugin"/> </type> - <type name="Magento\InventoryBundleProductIndexer\Indexer\SiblingProductsProvider"> + <type name="Magento\InventoryIndexer\Indexer\Stock\DataProvider"> <arguments> - <argument name="selectBuilder" xsi:type="object">Magento\InventoryBundleProductIndexer\Indexer\SelectBuilder</argument> + <argument name="siblingSelectBuilders" xsi:type="array"> + <item name="bundle" xsi:type="object">Magento\InventoryBundleProductIndexer\Indexer\SelectBuilder</item> + </argument> </arguments> </type> - <type name="Magento\InventoryIndexer\Indexer\Stock\IndexDataFiller"> + <type name="Magento\InventoryIndexer\Indexer\SiblingProductsProvidersPool"> <arguments> <argument name="siblingProductsProviders" xsi:type="array"> <item name="bundle" xsi:type="object">Magento\InventoryBundleProductIndexer\Indexer\SiblingProductsProvider</item> diff --git a/InventoryCatalog/Model/CompositeProductTypesProvider.php b/InventoryCatalog/Model/CompositeProductTypesProvider.php new file mode 100644 index 00000000000..1e7b39c13e5 --- /dev/null +++ b/InventoryCatalog/Model/CompositeProductTypesProvider.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryCatalog\Model; + +use Magento\Catalog\Model\ProductTypes\ConfigInterface as ProductTypesConfig; +use Magento\InventoryCatalogApi\Model\CompositeProductTypesProviderInterface; + +class CompositeProductTypesProvider implements CompositeProductTypesProviderInterface +{ + /** + * @param ProductTypesConfig $productTypesConfig + */ + public function __construct( + private readonly ProductTypesConfig $productTypesConfig, + ) { + } + + /** + * @inheritdoc + */ + public function execute(): array + { + $compositeTypes = array_filter( + $this->productTypesConfig->getAll(), + fn (array $type) => ($type['composite'] ?? false) === true, + ); + + return array_keys($compositeTypes); + } +} diff --git a/InventoryCatalog/Model/GetChildrenSkusOfParentSkus.php b/InventoryCatalog/Model/GetChildrenSkusOfParentSkus.php new file mode 100644 index 00000000000..87228993aa2 --- /dev/null +++ b/InventoryCatalog/Model/GetChildrenSkusOfParentSkus.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryCatalog\Model; + +use Magento\Catalog\Model\ResourceModel\Product\Relation; +use Magento\InventoryCatalogApi\Model\GetProductIdsBySkusInterface; +use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface; +use Magento\InventoryCatalogApi\Model\GetChildrenSkusOfParentSkusInterface; + +/** + * @inheritdoc + */ +class GetChildrenSkusOfParentSkus implements GetChildrenSkusOfParentSkusInterface +{ + /** + * @param Relation $productRelationResource + * @param GetProductIdsBySkusInterface $getProductIdsBySkus + * @param GetSkusByProductIdsInterface $getSkusByProductIds + */ + public function __construct( + private readonly Relation $productRelationResource, + private readonly GetProductIdsBySkusInterface $getProductIdsBySkus, + private readonly GetSkusByProductIdsInterface $getSkusByProductIds + ) { + } + + /** + * @inheritdoc + */ + public function execute(array $skus): array + { + if (!$skus) { + return []; + } + + $parentIds = $this->getProductIdsBySkus->execute($skus); + $childIdsOfParentIds = $this->productRelationResource->getRelationsByParent(array_values($parentIds)); + $flatChildIds = array_merge([], ...$childIdsOfParentIds); + $childSkus = $flatChildIds ? $this->getSkusByProductIds->execute(array_unique($flatChildIds)) : []; + + $childSkusOfParentSkus = []; + foreach ($skus as $sku) { + $parentId = $parentIds[$sku]; + $childSkusOfParentSkus[$sku] = array_map( + fn ($childId) => $childSkus[$childId], + $childIdsOfParentIds[$parentId] ?? [] + ); + } + + return $childSkusOfParentSkus; + } +} diff --git a/InventoryCatalog/Test/Unit/Model/GetChildrenSkusOfParentSkusTest.php b/InventoryCatalog/Test/Unit/Model/GetChildrenSkusOfParentSkusTest.php new file mode 100644 index 00000000000..eedfff6ccc7 --- /dev/null +++ b/InventoryCatalog/Test/Unit/Model/GetChildrenSkusOfParentSkusTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryCatalog\Test\Unit\Model; + +use Magento\Catalog\Model\ResourceModel\Product\Relation; +use Magento\InventoryCatalog\Model\GetChildrenSkusOfParentSkus; +use Magento\InventoryCatalogApi\Model\GetProductIdsBySkusInterface; +use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class GetChildrenSkusOfParentSkusTest extends TestCase +{ + /** + * @var Relation|MockObject + */ + private $productRelationResourceMock; + + /** + * @var GetProductIdsBySkusInterface|MockObject + */ + private $getProductIdsBySkusMock; + + /** + * @var GetSkusByProductIdsInterface|MockObject + */ + private $getSkusByProductIdsMock; + + /** + * @var GetChildrenSkusOfParentSkus + */ + private $model; + + protected function setUp(): void + { + $this->productRelationResourceMock = $this->createMock(Relation::class); + $this->getProductIdsBySkusMock = $this->createMock(GetProductIdsBySkusInterface::class); + $this->getSkusByProductIdsMock = $this->createMock(GetSkusByProductIdsInterface::class); + $this->model = new GetChildrenSkusOfParentSkus( + $this->productRelationResourceMock, + $this->getProductIdsBySkusMock, + $this->getSkusByProductIdsMock, + ); + } + + public function testExecuteNoSkus(): void + { + $this->getProductIdsBySkusMock->expects(self::never())->method('execute'); + $this->productRelationResourceMock->expects(self::never())->method('getRelationsByParent'); + $this->model->execute([]); + } + + public function testExecute(): void + { + $childrenSkusOfParentSkus = [ + 'configurable1' => ['simple-1'], + 'grouped1' => [], + 'bundle1' => ['simple-1', 'simple-2'], + ]; + + $this->getProductIdsBySkusMock->expects(self::once())->method('execute') + ->with(array_keys($childrenSkusOfParentSkus)) + ->willReturn(['configurable1' => 10, 'grouped1' => 20, 'bundle1' => 30]); + $this->productRelationResourceMock->expects(self::once())->method('getRelationsByParent') + ->with([10, 20, 30]) + ->willReturn([10 => [2], 20 => [], 30 => [2, 3]]); + $this->getSkusByProductIdsMock->expects(self::once())->method('execute') + ->with(self::equalToCanonicalizing([2, 3])) + ->willReturn([2 => 'simple-1', 3 => 'simple-2']); + + $result = $this->model->execute(array_keys($childrenSkusOfParentSkus)); + self::assertEquals($childrenSkusOfParentSkus, $result); + } +} diff --git a/InventoryCatalog/etc/di.xml b/InventoryCatalog/etc/di.xml index 1804eefed78..cf5c11428a6 100644 --- a/InventoryCatalog/etc/di.xml +++ b/InventoryCatalog/etc/di.xml @@ -12,10 +12,13 @@ <preference for="Magento\InventoryCatalogApi\Model\GetProductTypesBySkusInterface" type="Magento\InventoryCatalog\Model\ResourceModel\GetProductTypesBySkusCache" /> <preference for="Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface" type="Magento\InventoryCatalog\Model\GetSkusByProductIdsCache"/> <preference for="Magento\InventoryCatalogApi\Model\GetParentSkusOfChildrenSkusInterface" type="Magento\InventoryCatalog\Model\GetParentSkusOfChildrenSkus"/> + <preference for="Magento\InventoryCatalogApi\Model\GetChildrenSkusOfParentSkusInterface" type="Magento\InventoryCatalog\Model\GetChildrenSkusOfParentSkus"/> <preference for="Magento\InventoryCatalogApi\Model\IsSingleSourceModeInterface" type="Magento\InventoryCatalog\Model\IsSingleSourceModeCache"/> <preference for="Magento\InventoryCatalogApi\Model\SourceItemsProcessorInterface" type="Magento\InventoryCatalog\Model\SourceItemsProcessor"/> <preference for="Magento\CatalogInventory\Model\StockStatusApplierInterface" type="Magento\InventoryCatalog\Model\StockStatusApplier" /> <preference for="Magento\CatalogInventory\Observer\SaveInventoryDataObserver" type="Magento\InventoryCatalog\Observer\SaveInventoryDataObserver"/> + <preference for="Magento\InventoryCatalogApi\Model\CompositeProductTypesProviderInterface" type="Magento\InventoryCatalog\Model\CompositeProductTypesProvider"/> + <type name="Magento\InventoryApi\Api\StockRepositoryInterface"> <plugin name="prevent_default_stock_deleting" type="Magento\InventoryCatalog\Plugin\InventoryApi\StockRepository\PreventDeleting\DefaultStockPlugin"/> diff --git a/InventoryCatalogApi/Model/CompositeProductTypesProviderInterface.php b/InventoryCatalogApi/Model/CompositeProductTypesProviderInterface.php new file mode 100644 index 00000000000..fabe1b74c2b --- /dev/null +++ b/InventoryCatalogApi/Model/CompositeProductTypesProviderInterface.php @@ -0,0 +1,18 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryCatalogApi\Model; + +interface CompositeProductTypesProviderInterface +{ + /** + * Returns composite product types. + * + * @return string[] + */ + public function execute(): array; +} diff --git a/InventoryCatalogApi/Model/GetChildrenSkusOfParentSkusInterface.php b/InventoryCatalogApi/Model/GetChildrenSkusOfParentSkusInterface.php new file mode 100644 index 00000000000..cba302bd587 --- /dev/null +++ b/InventoryCatalogApi/Model/GetChildrenSkusOfParentSkusInterface.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryCatalogApi\Model; + +/** + * Provides relational children product SKUs by given parent SKUs + */ +interface GetChildrenSkusOfParentSkusInterface +{ + /** + * Returns children SKUs of parent SKUs. + * + * Resulting array is like: + * ```php + * [ + * 'bundle1' => [], + * 'configurable1' => ['configurable1-red', 'configurable1-green'], + * 'grouped1' => ['simple1'], + * ] + * ``` + * + * @param string[] $skus Parents SKUs + * @return array<string, string[]> Array of children SKUs arrays that belong to parents SKUs + */ + public function execute(array $skus): array; +} diff --git a/InventoryCatalogSearchConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductNotVisibleAfterDisablingChildProductsTest.xml b/InventoryCatalogSearchConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductNotVisibleAfterDisablingChildProductsTest.xml index 52ce978fc03..4342f8c5a99 100644 --- a/InventoryCatalogSearchConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductNotVisibleAfterDisablingChildProductsTest.xml +++ b/InventoryCatalogSearchConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductNotVisibleAfterDisablingChildProductsTest.xml @@ -114,6 +114,7 @@ <argument name="websiteName" value="{{_defaultWebsite.name}}"/> </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <!--Disable source.--> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableSources"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdminArea"/> diff --git a/InventoryConfigurableProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php b/InventoryConfigurableProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php new file mode 100644 index 00000000000..2115c764819 --- /dev/null +++ b/InventoryConfigurableProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryConfigurableProduct\Test\Integration\Model; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable as ConfigurableType; +use Magento\InventoryCatalogApi\Model\CompositeProductTypesProviderInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +class CompositeProductTypesProviderTest extends TestCase +{ + public function testExecute(): void + { + $compositeProductTypesProvider = Bootstrap::getObjectManager() + ->create(CompositeProductTypesProviderInterface::class); + $compositeProductTypes = $compositeProductTypesProvider->execute(); + self::assertContains(ConfigurableType::TYPE_CODE, $compositeProductTypes); + } +} diff --git a/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php b/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php index b7781a779ed..bfff352d036 100644 --- a/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php +++ b/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php @@ -13,55 +13,40 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\IndexStructure; +use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryIndexer\Indexer\SiblingSelectBuilderInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameResolverInterface; class SelectBuilder implements SiblingSelectBuilderInterface { - /** - * @var ResourceConnection - */ - private $resourceConnection; - - /** - * @var IndexNameResolverInterface - */ - private $indexNameResolver; - - /** - * @var MetadataPool - */ - private $metadataPool; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @param ResourceConnection $resourceConnection + * @param IndexNameBuilder $indexNameBuilder * @param IndexNameResolverInterface $indexNameResolver * @param MetadataPool $metadataPool * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( - ResourceConnection $resourceConnection, - IndexNameResolverInterface $indexNameResolver, - MetadataPool $metadataPool, - DefaultStockProviderInterface $defaultStockProvider + private readonly ResourceConnection $resourceConnection, + private readonly IndexNameBuilder $indexNameBuilder, + private readonly IndexNameResolverInterface $indexNameResolver, + private readonly MetadataPool $metadataPool, + private readonly DefaultStockProviderInterface $defaultStockProvider ) { - $this->resourceConnection = $resourceConnection; - $this->indexNameResolver = $indexNameResolver; - $this->metadataPool = $metadataPool; - $this->defaultStockProvider = $defaultStockProvider; } /** * @inheritdoc */ - public function getSelect(IndexName $indexName, array $skuList = []): Select + public function getSelect(int $stockId, array $skuList = [], IndexAlias $indexAlias = IndexAlias::MAIN): Select { $connection = $this->resourceConnection->getConnection(); + $indexName = $this->indexNameBuilder->setIndexId(InventoryIndexer::INDEXER_ID) + ->addDimension('stock_', (string) $stockId) + ->setAlias($indexAlias->value) + ->build(); $indexTableName = $this->indexNameResolver->resolveName($indexName); $metadata = $this->metadataPool->getMetadata(ProductInterface::class); $linkField = $metadata->getLinkField(); diff --git a/InventoryConfigurableProductIndexer/Indexer/SiblingProductsProvider.php b/InventoryConfigurableProductIndexer/Indexer/SiblingProductsProvider.php index b629245764f..0567b983ccc 100644 --- a/InventoryConfigurableProductIndexer/Indexer/SiblingProductsProvider.php +++ b/InventoryConfigurableProductIndexer/Indexer/SiblingProductsProvider.php @@ -11,20 +11,16 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\EntityManager\MetadataPool; use Magento\InventoryIndexer\Indexer\SiblingProductsProviderInterface; -use Magento\InventoryIndexer\Indexer\SiblingSelectBuilderInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; class SiblingProductsProvider implements SiblingProductsProviderInterface { /** * @param ResourceConnection $resourceConnection * @param MetadataPool $metadataPool - * @param SiblingSelectBuilderInterface $selectBuilder */ public function __construct( private readonly ResourceConnection $resourceConnection, private readonly MetadataPool $metadataPool, - private readonly SiblingSelectBuilderInterface $selectBuilder, ) { } @@ -56,16 +52,4 @@ public function getSkus(array $skus): array return $siblingSkus; } - - /** - * @inheritdoc - */ - public function getData(IndexName $indexName, array $skuList = []): array - { - $connection = $this->resourceConnection->getConnection(); - $select = $this->selectBuilder->getSelect($indexName, $skuList); - $data = $connection->fetchAll($select); - - return $data; - } } diff --git a/InventoryConfigurableProductIndexer/Indexer/SourceItem/SiblingSkuListInStockProvider.php b/InventoryConfigurableProductIndexer/Indexer/SourceItem/SiblingSkuListInStockProvider.php deleted file mode 100644 index e21f0c841b1..00000000000 --- a/InventoryConfigurableProductIndexer/Indexer/SourceItem/SiblingSkuListInStockProvider.php +++ /dev/null @@ -1,147 +0,0 @@ -<?php -/** - * Copyright 2018 Adobe - * All Rights Reserved. - */ -declare(strict_types=1); - -namespace Magento\InventoryConfigurableProductIndexer\Indexer\SourceItem; - -use Exception; -use Magento\Framework\App\ResourceConnection; -use Magento\InventoryApi\Api\Data\SourceItemInterface; -use Magento\InventoryApi\Api\Data\StockSourceLinkInterface; -use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStock; -use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Catalog\Api\Data\ProductInterface; - -/** - * Returns relations between stock and sku list - */ -class SiblingSkuListInStockProvider -{ - /** - * @var ResourceConnection - */ - private $resourceConnection; - - /** - * @var SkuListInStockFactory - */ - private $skuListInStockFactory; - - /** - * @var MetadataPool - */ - private $metadataPool; - - /** - * @var string - */ - private $tableNameSourceItem; - - /** - * @var string - */ - private $tableNameStockSourceLink; - - /** - * GetSkuListInStock constructor. - * - * @param ResourceConnection $resourceConnection - * @param SkuListInStockFactory $skuListInStockFactory - * @param MetadataPool $metadataPool - * @param string $tableNameSourceItem - * @param string $tableNameStockSourceLink - */ - public function __construct( - ResourceConnection $resourceConnection, - SkuListInStockFactory $skuListInStockFactory, - MetadataPool $metadataPool, - $tableNameSourceItem, - $tableNameStockSourceLink - ) { - $this->resourceConnection = $resourceConnection; - $this->skuListInStockFactory = $skuListInStockFactory; - $this->metadataPool = $metadataPool; - $this->tableNameSourceItem = $tableNameSourceItem; - $this->tableNameStockSourceLink = $tableNameStockSourceLink; - } - - /** - * Returns all assigned Stock ids by given Source Item ids - * - * @param int[] $sourceItemIds - * @return SkuListInStock[] List of stock id to sku1,sku2 assignment - * @throws Exception - */ - public function execute(array $sourceItemIds): array - { - $connection = $this->resourceConnection->getConnection(); - $sourceStockLinkTable = $this->resourceConnection->getTableName($this->tableNameStockSourceLink); - $sourceItemTable = $this->resourceConnection->getTableName($this->tableNameSourceItem); - - $metadata = $this->metadataPool->getMetadata(ProductInterface::class); - $items = []; - - $select = $connection - ->select() - ->from( - ['source_item' => $sourceItemTable], - [] - )->joinInner( - ['stock_source_link' => $sourceStockLinkTable], - sprintf( - 'source_item.%s = stock_source_link.%s', - SourceItemInterface::SOURCE_CODE, - StockSourceLinkInterface::SOURCE_CODE - ), - [StockSourceLinkInterface::STOCK_ID] - )->joinInner( - ['child_product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')], - 'child_product_entity.sku = source_item.sku', - [] - )->joinInner( - ['parent_link' => $this->resourceConnection->getTableName('catalog_product_super_link')], - 'parent_link.product_id = child_product_entity.' . $metadata->getIdentifierField(), - [] - )->joinInner( - ['sibling_product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')], - 'sibling_product_entity.' . $metadata->getLinkField() . ' = parent_link.parent_id', - ['sku' => 'sibling_product_entity.sku'] - )->where( - 'source_item.source_item_id IN (?)', - $sourceItemIds - )->group( - ['stock_source_link.' . StockSourceLinkInterface::STOCK_ID, 'sibling_product_entity.sku'] - ); - - $dbStatement = $connection->query($select); - while ($item = $dbStatement->fetch()) { - $items[$item[StockSourceLinkInterface::STOCK_ID]][$item[SourceItemInterface::SKU]] = - $item[SourceItemInterface::SKU]; - } - - return $this->getStockIdToSkuList($items); - } - - /** - * Return the assigned stock id to sku list - * - * @param array $items - * @return SkuListInStock[] - */ - private function getStockIdToSkuList(array $items): array - { - $skuListInStockList = []; - foreach ($items as $stockId => $skuList) { - /** @var SkuListInStock $skuListInStock */ - $skuListInStock = $this->skuListInStockFactory->create(); - $skuListInStock->setStockId((int)$stockId); - $skuListInStock->setSkuList($skuList); - $skuListInStockList[] = $skuListInStock; - } - return $skuListInStockList; - } -} diff --git a/InventoryConfigurableProductIndexer/Indexer/SourceItem/SourceItemIndexer.php b/InventoryConfigurableProductIndexer/Indexer/SourceItem/SourceItemIndexer.php deleted file mode 100644 index c7b3da034ca..00000000000 --- a/InventoryConfigurableProductIndexer/Indexer/SourceItem/SourceItemIndexer.php +++ /dev/null @@ -1,160 +0,0 @@ -<?php -/** - * Copyright 2018 Adobe - * All Rights Reserved. - */ -declare(strict_types=1); - -namespace Magento\InventoryConfigurableProductIndexer\Indexer\SourceItem; - -use Magento\Framework\App\DeploymentConfig; -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Indexer\SaveHandler\Batch; -use Magento\InventoryIndexer\Indexer\SiblingProductsProviderInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexHandlerInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexStructureInterface; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; -use Magento\InventoryIndexer\Indexer\InventoryIndexer; -use ArrayIterator; - -/** - * Configurable product source item indexer - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) Will be removed after deleting DefaultStockProviderInterface - */ -class SourceItemIndexer -{ - /** - * @var IndexNameBuilder - */ - private $indexNameBuilder; - - /** - * @var IndexHandlerInterface - */ - private $indexHandler; - - /** - * @var IndexStructureInterface - */ - private $indexStructure; - - /** - * @var SiblingSkuListInStockProvider - */ - private $siblingSkuListInStockProvider; - - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - - /** - * @var int - */ - private $batchSize; - - /** - * @var Batch - */ - private $batch; - - /** - * @var DeploymentConfig - */ - private $deploymentConfig; - - /** - * Deployment config path - * - * @var string - */ - private const DEPLOYMENT_CONFIG_INDEXER_BATCHES = 'indexer/batch_size/'; - - /** - * @param IndexNameBuilder $indexNameBuilder - * @param IndexHandlerInterface $indexHandler - * @param IndexStructureInterface $indexStructure - * @param SiblingSkuListInStockProvider $siblingSkuListInStockProvider - * @param DefaultStockProviderInterface $defaultStockProvider - * @param Batch $batch - * @param DeploymentConfig $deploymentConfig - * @param SiblingProductsProviderInterface $productsProvider - * @param int $batchSize - */ - public function __construct( - IndexNameBuilder $indexNameBuilder, - IndexHandlerInterface $indexHandler, - IndexStructureInterface $indexStructure, - SiblingSkuListInStockProvider $siblingSkuListInStockProvider, - DefaultStockProviderInterface $defaultStockProvider, - Batch $batch, - DeploymentConfig $deploymentConfig, - private readonly SiblingProductsProviderInterface $productsProvider, - int $batchSize = 100, - ) { - $this->indexNameBuilder = $indexNameBuilder; - $this->indexHandler = $indexHandler; - $this->indexStructure = $indexStructure; - $this->siblingSkuListInStockProvider = $siblingSkuListInStockProvider; - $this->defaultStockProvider = $defaultStockProvider; - $this->batch = $batch; - $this->deploymentConfig = $deploymentConfig; - $this->batchSize = $batchSize; - } - - /** - * Executes index by list of stock ids - * - * @param array $sourceItemIds - */ - public function executeList(array $sourceItemIds) - { - $skuListInStockList = $this->siblingSkuListInStockProvider->execute($sourceItemIds); - $this->batchSize = $this->deploymentConfig->get( - self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . InventoryIndexer::INDEXER_ID . '/' . 'configurable' - ) ?? - $this->deploymentConfig->get( - self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . InventoryIndexer::INDEXER_ID . '/' . 'default' - ) - ?? $this->batchSize; - - foreach ($skuListInStockList as $skuListInStock) { - $stockId = $skuListInStock->getStockId(); - - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } - $skuList = $skuListInStock->getSkuList(); - - $mainIndexName = $this->indexNameBuilder - ->setIndexId(InventoryIndexer::INDEXER_ID) - ->addDimension('stock_', (string)$stockId) - ->setAlias(Alias::ALIAS_MAIN) - ->build(); - - if (!$this->indexStructure->isExist($mainIndexName, ResourceConnection::DEFAULT_CONNECTION)) { - $this->indexStructure->create($mainIndexName, ResourceConnection::DEFAULT_CONNECTION); - } - - $data = $this->productsProvider->getData($mainIndexName, $skuList); - $indexData = new ArrayIterator($data); - foreach ($this->batch->getItems($indexData, $this->batchSize) as $batchData) { - $batchIndexData = new ArrayIterator($batchData); - $this->indexHandler->cleanIndex( - $mainIndexName, - $batchIndexData, - ResourceConnection::DEFAULT_CONNECTION - ); - - $this->indexHandler->saveIndex( - $mainIndexName, - $batchIndexData, - ResourceConnection::DEFAULT_CONNECTION - ); - } - } - } -} diff --git a/InventoryConfigurableProductIndexer/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/APISourceItemIndexerPlugin.php b/InventoryConfigurableProductIndexer/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/APISourceItemIndexerPlugin.php index dced0d60b0a..47ef91f04b2 100644 --- a/InventoryConfigurableProductIndexer/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/APISourceItemIndexerPlugin.php +++ b/InventoryConfigurableProductIndexer/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/APISourceItemIndexerPlugin.php @@ -11,78 +11,27 @@ use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Model\AbstractModel; -use Magento\InventoryApi\Api\GetSourceItemsBySkuInterface; -use Magento\InventoryCatalogApi\Api\DefaultSourceProviderInterface; +use Magento\InventoryApi\Model\GetStockIdsBySkusInterface; use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface; -use Magento\InventoryConfigurableProductIndexer\Indexer\SourceItem\SourceItemIndexer; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; class APISourceItemIndexerPlugin { /** - * @var SourceItemIndexer - */ - private SourceItemIndexer $configurableProductsSourceItemIndexer; - - /** - * @var GetSourceItemsBySkuInterface - */ - private GetSourceItemsBySkuInterface $getSourceItemsBySku; - - /** - * @var DefaultSourceProviderInterface - */ - private DefaultSourceProviderInterface $defaultSourceProvider; - - /** - * @var GetSkusByProductIdsInterface - */ - private GetSkusByProductIdsInterface $skuProvider; - - /** - * @param SourceItemIndexer $configurableProductsSourceItemIndexer - * @param GetSourceItemsBySkuInterface $getSourceItemsBySku - * @param DefaultSourceProviderInterface $defaultSourceProvider - * @param GetSkusByProductIdsInterface $getSkusByProductIdsInterface + * @param Configurable $configurableType + * @param GetSkusByProductIdsInterface $getSkusByProductIds + * @param GetStockIdsBySkusInterface $getStockIdsBySkus + * @param SkuListInStockFactory $skuListInStockFactory + * @param SkuListsProcessor $skuListsProcessor */ public function __construct( - SourceItemIndexer $configurableProductsSourceItemIndexer, - GetSourceItemsBySkuInterface $getSourceItemsBySku, - DefaultSourceProviderInterface $defaultSourceProvider, - GetSkusByProductIdsInterface $getSkusByProductIdsInterface + private readonly Configurable $configurableType, + private readonly GetSkusByProductIdsInterface $getSkusByProductIds, + private readonly GetStockIdsBySkusInterface $getStockIdsBySkus, + private readonly SkuListInStockFactory $skuListInStockFactory, + private readonly SkuListsProcessor $skuListsProcessor, ) { - $this->configurableProductsSourceItemIndexer = $configurableProductsSourceItemIndexer; - $this->getSourceItemsBySku = $getSourceItemsBySku; - $this->defaultSourceProvider = $defaultSourceProvider; - $this->skuProvider = $getSkusByProductIdsInterface; - } - - /** - * Extracts product source item ids - * - * @param array $childProductIds - * @return array - * @throws NoSuchEntityException - */ - private function getProductSourceItemIds(array $childProductIds): array - { - $sourceItemIds = []; - foreach ($childProductIds as $productIds) { - if (empty($productIds)) { - continue; - } - foreach ($this->skuProvider->execute($productIds) as $childSku) { - $sourceItems = $this->getSourceItemsBySku->execute($childSku); - foreach ($sourceItems as $key => $sourceItem) { - if ($sourceItem->getSourceCode() === $this->defaultSourceProvider->getCode()) { - unset($sourceItems[$key]); - continue; - } - $sourceItemIds[] = $sourceItem->getId(); - } - } - } - - return $sourceItemIds; } /** @@ -104,10 +53,23 @@ public function afterSave( return $result; } - $childProductIds = $product->getTypeInstance()->getChildrenIds($product->getId()); - $sourceItemIds = $this->getProductSourceItemIds($childProductIds); - if ($sourceItemIds) { - $this->configurableProductsSourceItemIndexer->executeList($sourceItemIds); + $childProductIds = $this->configurableType->getChildrenIds($product->getId())[0]; + if (!$childProductIds) { + return $result; + } + + $childProductSkus = $this->getSkusByProductIds->execute($childProductIds); + $stockIds = $this->getStockIdsBySkus->execute($childProductSkus); + if ($stockIds) { + $skuListInStockList = []; + foreach ($stockIds as $stockId) { + $skuListInStock = $this->skuListInStockFactory->create( + ['stockId' => $stockId, 'skuList' => [$product->getSku()]] + ); + $skuListInStockList[] = $skuListInStock; + } + $this->skuListsProcessor->reindexList($skuListInStockList); + $product->setIsChangedCategories(true); $product->setAffectedCategoryIds($product->getCategoryIds()); $product->cleanModelCache(); diff --git a/InventoryConfigurableProductIndexer/Test/Integration/Indexer/SiblingProductsProviderTest.php b/InventoryConfigurableProductIndexer/Test/Integration/Indexer/SelectBuilderTest.php similarity index 54% rename from InventoryConfigurableProductIndexer/Test/Integration/Indexer/SiblingProductsProviderTest.php rename to InventoryConfigurableProductIndexer/Test/Integration/Indexer/SelectBuilderTest.php index 8ebce29c64a..a88f01b255a 100644 --- a/InventoryConfigurableProductIndexer/Test/Integration/Indexer/SiblingProductsProviderTest.php +++ b/InventoryConfigurableProductIndexer/Test/Integration/Indexer/SelectBuilderTest.php @@ -7,25 +7,28 @@ namespace Magento\InventoryConfigurableProductIndexer\Test\Integration\Indexer; -use Magento\InventoryConfigurableProductIndexer\Indexer\SiblingProductsProvider; -use Magento\InventoryIndexer\Indexer\InventoryIndexer; -use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable as ConfigurableType; +use Magento\InventoryConfigurableProductIndexer\Indexer\SelectBuilder; +use Magento\InventoryIndexer\Indexer\Stock\DataProvider; use Magento\TestFramework\Fixture\DataFixture; use Magento\TestFramework\Fixture\DbIsolation; use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -class SiblingProductsProviderTest extends TestCase +#[ + CoversClass(SelectBuilder::class), +] +class SelectBuilderTest extends TestCase { /** - * @var SiblingProductsProvider + * @var DataProvider */ - private $siblingProductsProvider; + private $dataProvider; protected function setUp(): void { - $this->siblingProductsProvider = Bootstrap::getObjectManager()->create(SiblingProductsProvider::class); + $this->dataProvider = Bootstrap::getObjectManager()->create(DataProvider::class); } #[ @@ -42,17 +45,12 @@ protected function setUp(): void public function testGetSelect(): void { $stockId = 10; - $skus = []; - - $indexNameBuilder = Bootstrap::getObjectManager()->get(IndexNameBuilder::class); - $indexName = $indexNameBuilder->setIndexId(InventoryIndexer::INDEXER_ID) - ->addDimension('stock_', (string) $stockId) - ->setAlias(Alias::ALIAS_MAIN) - ->build(); - $indexData = $this->siblingProductsProvider->getData($indexName); - foreach ($indexData as $item) { - $skus[] = $item['sku']; - } - $this->assertContains('configurable_1', $skus); + $sku = 'configurable_1'; + $indexData = $this->dataProvider->getSiblingsData($stockId, ConfigurableType::TYPE_CODE, [$sku]); + self::assertCount(1, $indexData); + $row = reset($indexData); + self::assertEquals($sku, $row['sku']); + self::assertEquals(300, $row['quantity']); + self::assertEquals(1, $row['is_salable']); } } diff --git a/InventoryConfigurableProductIndexer/Test/Unit/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/APISourceItemIndexerPluginTest.php b/InventoryConfigurableProductIndexer/Test/Unit/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/APISourceItemIndexerPluginTest.php index 556e04680be..c42b6e6acf0 100644 --- a/InventoryConfigurableProductIndexer/Test/Unit/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/APISourceItemIndexerPluginTest.php +++ b/InventoryConfigurableProductIndexer/Test/Unit/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/APISourceItemIndexerPluginTest.php @@ -12,110 +12,100 @@ use Magento\Catalog\Model\Product\Type\AbstractType; use Magento\Catalog\Model\ResourceModel\Product as ProductResource; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; -use Magento\Inventory\Model\SourceItem; -use Magento\InventoryApi\Api\GetSourceItemsBySkuInterface; -use Magento\InventoryCatalogApi\Api\DefaultSourceProviderInterface; +use Magento\InventoryApi\Model\GetStockIdsBySkusInterface; use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface; -use Magento\InventoryConfigurableProductIndexer\Indexer\SourceItem\SourceItemIndexer; use Magento\InventoryConfigurableProductIndexer\Plugin\InventoryIndexer\Indexer\SourceItem\Strategy\Sync\APISourceItemIndexerPlugin; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStock; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; // @codingStandardsIgnoreEnd -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ class APISourceItemIndexerPluginTest extends TestCase { /** - * @var SourceItemIndexer|MockObject + * @var APISourceItemIndexerPlugin */ - private SourceItemIndexer $configurableProductsSourceItemIndexer; + private $plugin; /** - * @var GetSourceItemsBySkuInterface|MockObject + * @var Configurable|MockObject */ - private GetSourceItemsBySkuInterface $getSourceItemsBySku; + private $typeInstanceMock; /** - * @var DefaultSourceProviderInterface|MockObject + * @var GetSkusByProductIdsInterface|MockObject */ - private DefaultSourceProviderInterface $defaultSourceProvider; + private $getSkusByProductIdsMock; /** - * @var GetSkusByProductIdsInterface|MockObject + * @var GetStockIdsBySkusInterface|MockObject + */ + private $getStockIdsBySkusMock; + + /** + * @var SkuListInStockFactory|MockObject */ - private GetSkusByProductIdsInterface $skuProvider; + private $skuListInStockFactoryMock; /** - * @var APISourceItemIndexerPlugin|MockObject + * @var SkuListsProcessor|MockObject */ - private APISourceItemIndexerPlugin $plugin; + private $skuListsProcessorMock; protected function setUp(): void { - $this->configurableProductsSourceItemIndexer = $this->createMock(SourceItemIndexer::class); - $this->getSourceItemsBySku = $this->createMock(GetSourceItemsBySkuInterface::class); - $this->defaultSourceProvider = $this->createMock(DefaultSourceProviderInterface::class); - $this->skuProvider = $this->createMock(GetSkusByProductIdsInterface::class); + parent::setUp(); + $this->typeInstanceMock = $this->createMock(Configurable::class); + $this->getSkusByProductIdsMock = $this->createMock(GetSkusByProductIdsInterface::class); + $this->getStockIdsBySkusMock = $this->createMock(GetStockIdsBySkusInterface::class); + $this->skuListInStockFactoryMock = $this->createMock(SkuListInStockFactory::class); + $this->skuListsProcessorMock = $this->createMock(SkuListsProcessor::class); $this->plugin = new APISourceItemIndexerPlugin( - $this->configurableProductsSourceItemIndexer, - $this->getSourceItemsBySku, - $this->defaultSourceProvider, - $this->skuProvider + $this->typeInstanceMock, + $this->getSkusByProductIdsMock, + $this->getStockIdsBySkusMock, + $this->skuListInStockFactoryMock, + $this->skuListsProcessorMock, ); - - parent::setUp(); } public function testAfterSave() { + $confId = 1; + $confSku = 'configurable1'; + $stockId = 2; + $childIds = [11, 12]; + $childSkus = ['sku-11', 'sku-12']; + $subject = $this->createMock(ProductResource::class); $result = $this->createMock(ProductResource::class); $object = $this->createMock(Product::class); $object->expects($this->once())->method('getTypeId')->willReturn(Configurable::TYPE_CODE); - $typeInstance = $this->createMock(AbstractType::class); - $typeInstance->expects($this->once()) + $this->typeInstanceMock->expects($this->once()) ->method('getChildrenIds') - ->with(1) - ->willReturn( - [ - 0 => [11 => '11', 12 => '12'] - ] - ); + ->with($confId) + ->willReturn([$childIds]); - $object->expects($this->once())->method('getTypeInstance')->willReturn($typeInstance); - $object->expects($this->once())->method('getId')->willReturn(1); - $object->expects($this->once())->method('cleanModelCache'); - $this->defaultSourceProvider->expects($this->exactly(2))->method('getCode')->willreturn('default'); - $childSourceItem1 = $this->getSourceItem(1); - $childSourceItem2 = $this->getSourceItem(2); - $this->skuProvider->expects($this->once()) - ->method('execute') - ->with([11 => '11', 12 => '12']) - ->willReturn([11 => 'child-1', 12 => 'child-2']); - $this->getSourceItemsBySku->expects($this->exactly(2)) + $this->getSkusByProductIdsMock->expects($this->once()) ->method('execute') - ->willReturnCallback(function ($arg) use ($childSourceItem1, $childSourceItem2) { - if ($arg == 'child-1') { - return [$childSourceItem1]; - } elseif ($arg == 'child-2') { - return [$childSourceItem2]; - } - }); - $this->configurableProductsSourceItemIndexer->expects($this->once())->method('executeList')->with([1, 2]); + ->with($childIds) + ->willReturn($childSkus); + $this->getStockIdsBySkusMock->expects($this->once())->method('execute')->with($childSkus)->willReturn([2]); + $skuListInStockMock = $this->createMock(SkuListInStock::class); + $this->skuListInStockFactoryMock->expects($this->once()) + ->method('create') + ->with(['stockId' => $stockId, 'skuList' => [$confSku]]) + ->willReturn($skuListInStockMock); + $this->skuListsProcessorMock->expects($this->once())->method('reindexList')->with([$skuListInStockMock]); + + $object->expects($this->once())->method('getId')->willReturn($confId); + $object->expects($this->once())->method('getSku')->willReturn($confSku); + $object->expects($this->once())->method('cleanModelCache'); $interceptorResult = $this->plugin->afterSave($subject, $result, $object); $this->assertSame($interceptorResult, $result); } - - private function getSourceItem(int $returnValue): MockObject - { - $sourceItem = $this->createMock(SourceItem::class); - $sourceItem->expects($this->once())->method('getSourceCode')->willReturn('non-default-source'); - $sourceItem->expects($this->once())->method('getId')->willReturn($returnValue); - - return $sourceItem; - } } diff --git a/InventoryConfigurableProductIndexer/etc/di.xml b/InventoryConfigurableProductIndexer/etc/di.xml index 0beb1d0f4ed..b37de73a51c 100644 --- a/InventoryConfigurableProductIndexer/etc/di.xml +++ b/InventoryConfigurableProductIndexer/etc/di.xml @@ -7,23 +7,14 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="Magento\InventoryConfigurableProductIndexer\Indexer\SourceItem\SourceItemIndexer"> + <type name="Magento\InventoryIndexer\Indexer\Stock\DataProvider"> <arguments> - <argument name="productsProvider" xsi:type="object">Magento\InventoryConfigurableProductIndexer\Indexer\SiblingProductsProvider</argument> - </arguments> - </type> - <type name="Magento\InventoryConfigurableProductIndexer\Indexer\SourceItem\SiblingSkuListInStockProvider"> - <arguments> - <argument name="tableNameSourceItem" xsi:type="const">Magento\Inventory\Model\ResourceModel\SourceItem::TABLE_NAME_SOURCE_ITEM</argument> - <argument name="tableNameStockSourceLink" xsi:type="const">Magento\Inventory\Model\ResourceModel\StockSourceLink::TABLE_NAME_STOCK_SOURCE_LINK</argument> - </arguments> - </type> - <type name="Magento\InventoryConfigurableProductIndexer\Indexer\SiblingProductsProvider"> - <arguments> - <argument name="selectBuilder" xsi:type="object">Magento\InventoryConfigurableProductIndexer\Indexer\SelectBuilder</argument> + <argument name="siblingSelectBuilders" xsi:type="array"> + <item name="configurable" xsi:type="object">Magento\InventoryConfigurableProductIndexer\Indexer\SelectBuilder</item> + </argument> </arguments> </type> - <type name="Magento\InventoryIndexer\Indexer\Stock\IndexDataFiller"> + <type name="Magento\InventoryIndexer\Indexer\SiblingProductsProvidersPool"> <arguments> <argument name="siblingProductsProviders" xsi:type="array"> <item name="configurable" xsi:type="object">Magento\InventoryConfigurableProductIndexer\Indexer\SiblingProductsProvider</item> diff --git a/InventoryGroupedProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php b/InventoryGroupedProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php new file mode 100644 index 00000000000..2f6b1d5843b --- /dev/null +++ b/InventoryGroupedProduct/Test/Integration/Model/CompositeProductTypesProviderTest.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryGroupedProduct\Test\Integration\Model; + +use Magento\GroupedProduct\Model\Product\Type\Grouped as GroupedType; +use Magento\InventoryCatalogApi\Model\CompositeProductTypesProviderInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +class CompositeProductTypesProviderTest extends TestCase +{ + public function testExecute(): void + { + $compositeProductTypesProvider = Bootstrap::getObjectManager() + ->create(CompositeProductTypesProviderInterface::class); + $compositeProductTypes = $compositeProductTypesProvider->execute(); + self::assertContains(GroupedType::TYPE_CODE, $compositeProductTypes); + } +} diff --git a/InventoryGroupedProductIndexer/Indexer/SelectBuilder.php b/InventoryGroupedProductIndexer/Indexer/SelectBuilder.php index 1ea76b3bab7..307e3e0dcdb 100644 --- a/InventoryGroupedProductIndexer/Indexer/SelectBuilder.php +++ b/InventoryGroupedProductIndexer/Indexer/SelectBuilder.php @@ -13,8 +13,10 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\GroupedProduct\Model\ResourceModel\Product\Link; use Magento\InventoryIndexer\Indexer\IndexStructure; +use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryIndexer\Indexer\SiblingSelectBuilderInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameResolverInterface; /** @@ -22,42 +24,30 @@ */ class SelectBuilder implements SiblingSelectBuilderInterface { - /** - * @var ResourceConnection - */ - private $resourceConnection; - - /** - * @var IndexNameResolverInterface - */ - private $indexNameResolver; - - /** - * @var MetadataPool - */ - private $metadataPool; - /** * @param ResourceConnection $resourceConnection + * @param IndexNameBuilder $indexNameBuilder * @param IndexNameResolverInterface $indexNameResolver * @param MetadataPool $metadataPool */ public function __construct( - ResourceConnection $resourceConnection, - IndexNameResolverInterface $indexNameResolver, - MetadataPool $metadataPool, + private readonly ResourceConnection $resourceConnection, + private readonly IndexNameBuilder $indexNameBuilder, + private readonly IndexNameResolverInterface $indexNameResolver, + private readonly MetadataPool $metadataPool, ) { - $this->resourceConnection = $resourceConnection; - $this->indexNameResolver = $indexNameResolver; - $this->metadataPool = $metadataPool; } /** * @inheritdoc */ - public function getSelect(IndexName $indexName, array $skuList = []): Select + public function getSelect(int $stockId, array $skuList = [], IndexAlias $indexAlias = IndexAlias::MAIN): Select { $connection = $this->resourceConnection->getConnection(); + $indexName = $this->indexNameBuilder->setIndexId(InventoryIndexer::INDEXER_ID) + ->addDimension('stock_', (string) $stockId) + ->setAlias($indexAlias->value) + ->build(); $indexTableName = $this->indexNameResolver->resolveName($indexName); $metadata = $this->metadataPool->getMetadata(ProductInterface::class); $linkField = $metadata->getLinkField(); diff --git a/InventoryGroupedProductIndexer/Indexer/SiblingProductsProvider.php b/InventoryGroupedProductIndexer/Indexer/SiblingProductsProvider.php index aab9a0a2e98..f88abb479d9 100644 --- a/InventoryGroupedProductIndexer/Indexer/SiblingProductsProvider.php +++ b/InventoryGroupedProductIndexer/Indexer/SiblingProductsProvider.php @@ -11,20 +11,16 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\EntityManager\MetadataPool; use Magento\InventoryIndexer\Indexer\SiblingProductsProviderInterface; -use Magento\InventoryIndexer\Indexer\SiblingSelectBuilderInterface; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; class SiblingProductsProvider implements SiblingProductsProviderInterface { /** * @param ResourceConnection $resourceConnection * @param MetadataPool $metadataPool - * @param SiblingSelectBuilderInterface $selectBuilder */ public function __construct( private readonly ResourceConnection $resourceConnection, private readonly MetadataPool $metadataPool, - private readonly SiblingSelectBuilderInterface $selectBuilder, ) { } @@ -56,16 +52,4 @@ public function getSkus(array $skus): array return $siblingSkus; } - - /** - * @inheritdoc - */ - public function getData(IndexName $indexName, array $skuList = []): array - { - $connection = $this->resourceConnection->getConnection(); - $select = $this->selectBuilder->getSelect($indexName, $skuList); - $data = $connection->fetchAll($select); - - return $data; - } } diff --git a/InventoryGroupedProductIndexer/etc/di.xml b/InventoryGroupedProductIndexer/etc/di.xml index 121b5f4f18e..7b6246f32c7 100644 --- a/InventoryGroupedProductIndexer/etc/di.xml +++ b/InventoryGroupedProductIndexer/etc/di.xml @@ -7,12 +7,14 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="Magento\InventoryGroupedProductIndexer\Indexer\SiblingProductsProvider"> + <type name="Magento\InventoryIndexer\Indexer\Stock\DataProvider"> <arguments> - <argument name="selectBuilder" xsi:type="object">Magento\InventoryGroupedProductIndexer\Indexer\SelectBuilder</argument> + <argument name="siblingSelectBuilders" xsi:type="array"> + <item name="grouped" xsi:type="object">Magento\InventoryGroupedProductIndexer\Indexer\SelectBuilder</item> + </argument> </arguments> </type> - <type name="Magento\InventoryIndexer\Indexer\Stock\IndexDataFiller"> + <type name="Magento\InventoryIndexer\Indexer\SiblingProductsProvidersPool"> <arguments> <argument name="siblingProductsProviders" xsi:type="array"> <item name="grouped" xsi:type="object">Magento\InventoryGroupedProductIndexer\Indexer\SiblingProductsProvider</item> diff --git a/InventoryImportExport/Plugin/Import/SourceItemImporter.php b/InventoryImportExport/Plugin/Import/SourceItemImporter.php index 46477e23dfc..46e667e94ab 100644 --- a/InventoryImportExport/Plugin/Import/SourceItemImporter.php +++ b/InventoryImportExport/Plugin/Import/SourceItemImporter.php @@ -18,6 +18,7 @@ use Magento\InventoryApi\Api\SourceItemsSaveInterface; use Magento\InventoryCatalogApi\Api\DefaultSourceProviderInterface; use Magento\InventoryCatalogApi\Model\IsSingleSourceModeInterface; +use Magento\InventoryIndexer\Indexer\CompositeProductsIndexer; use Magento\InventoryIndexer\Indexer\SourceItem\SourceItemIndexer; /** @@ -51,6 +52,7 @@ class SourceItemImporter * @param SkuStorage $skuStorage * @param SourceItemResourceModel $sourceItemResourceModel * @param SourceItemIndexer $sourceItemIndexer + * @param CompositeProductsIndexer $compositeProductsIndexer */ public function __construct( private readonly SourceItemsSaveInterface $sourceItemsSave, @@ -59,7 +61,8 @@ public function __construct( private readonly IsSingleSourceModeInterface $isSingleSourceMode, private readonly SkuStorage $skuStorage, private readonly SourceItemResourceModel $sourceItemResourceModel, - private readonly SourceItemIndexer $sourceItemIndexer + private readonly SourceItemIndexer $sourceItemIndexer, + private readonly CompositeProductsIndexer $compositeProductsIndexer, ) { } @@ -84,6 +87,8 @@ public function afterProcess( array $importedData ): void { $sourceItems = []; + $skus = []; + $isSingleSourceMode = $this->isSingleSourceMode->execute(); // No need to load existing source items in single source mode as we know the only source is 'default' $existingSourceItemsBySKU = $isSingleSourceMode ? [] : $this->getSourceItems(array_keys($stockData)); @@ -91,6 +96,7 @@ public function afterProcess( $sourceItemIds = []; foreach ($stockData as $sku => $stockDatum) { $sku = (string)$sku; + $skus[] = $sku; $sources = $existingSourceItemsBySKU[$sku] ?? []; $isQtyExplicitlySet = (bool) ($importedData[$sku]['qty'] ?? false); $hasDefaultSource = isset($sources[$defaultSourceCode]); @@ -123,6 +129,10 @@ public function afterProcess( if (!empty($sourceItemIds)) { $this->sourceItemIndexer->executeList($sourceItemIds); } + + // Reindex composite products present in data. + // As they don't have their own source items, no reindex will be triggered automatically. + $this->compositeProductsIndexer->reindexList($skus); } /** diff --git a/InventoryImportExport/Test/Integration/Model/Import/ProductTest.php b/InventoryImportExport/Test/Integration/Model/Import/ProductTest.php index eecde09c621..53a912118d1 100644 --- a/InventoryImportExport/Test/Integration/Model/Import/ProductTest.php +++ b/InventoryImportExport/Test/Integration/Model/Import/ProductTest.php @@ -447,6 +447,54 @@ public function testShouldReindexCustomStockWhenBackordersConfigChanges(): void $this->assertEquals(0, $getProductSalableQty->execute($sku, $stock2)); } + #[ + AppArea(\Magento\Framework\App\Area::AREA_ADMINHTML), + DataFixture(SourceFixture::class, ['source_code' => 'source2']), + DataFixture(StockFixture::class, as: 'stock2'), + DataFixture( + StockSourceLinksFixture::class, + [['stock_id' => '$stock2.stock_id$', 'source_code' => 'source2']] + ), + DataFixture( + StockSalesChannelsFixture::class, + ['stock_id' => '$stock2.stock_id$', 'sales_channels' => ['base']] + ), + DataFixture(ProductFixture::class, ['sku' => 'simple1']), + DataFixture( + SourceItemsFixture::class, + [ + ['sku' => 'simple1', 'source_code' => 'default', 'quantity' => 0], + ['sku' => 'simple1', 'source_code' => 'source2', 'quantity' => 100], + ] + ), + DataFixture( + CsvFileFixture::class, + [ + 'rows' => [ + ['product_type', 'sku', 'name', 'product_websites', 'associated_skus', 'attribute_set_code'], + ['grouped', 'grouped1', 'Grouped Product 1', 'base', 'simple1=5', 'Default'], + ] + ], + 'grouped_product_import_file' + ), + ] + public function testShouldReindexCustomStockWhenCompositeProductImported(): void + { + $getStockItemData = Bootstrap::getObjectManager()->get(GetStockItemData::class); + $fixtures = DataFixtureStorageManager::getStorage(); + $stockId = (int) $fixtures->get('stock2')->getId(); + + $pathToFile = $fixtures->get('grouped_product_import_file')->getAbsolutePath(); + $productImporterModel = $this->getProductImporterModel($pathToFile); + $errors = $productImporterModel->validateData(); + self::assertEquals(0, $errors->getErrorsCount()); + $productImporterModel->importData(); + + $stockItemData = $getStockItemData->execute('grouped1', $stockId); + self::assertNotEmpty($stockItemData); + self::assertTrue((bool) $stockItemData[GetStockItemData::IS_SALABLE]); + } + /** * Process messages * @@ -508,12 +556,12 @@ private function buildDataArray(array $sourceItems): array * Return Product Importer Model for use with tests requires path to CSV import file * * @param string $pathToFile - * @param string $behavior + * @param string $behavior See Magento\ImportExport\Model\Source\Import\Behavior\Basic::toArray for proper mapping * @return Product */ private function getProductImporterModel( string $pathToFile, - string $behavior = Import::BEHAVIOR_ADD_UPDATE + string $behavior = Import::BEHAVIOR_APPEND ): Product { /** @var Filesystem\Directory\WriteInterface $directory */ $directory = $this->filesystem diff --git a/InventoryImportExport/Test/Unit/Plugin/Import/SourceItemImporterTest.php b/InventoryImportExport/Test/Unit/Plugin/Import/SourceItemImporterTest.php index 831982ef67a..66e99a688df 100644 --- a/InventoryImportExport/Test/Unit/Plugin/Import/SourceItemImporterTest.php +++ b/InventoryImportExport/Test/Unit/Plugin/Import/SourceItemImporterTest.php @@ -20,6 +20,7 @@ use Magento\InventoryCatalogApi\Api\DefaultSourceProviderInterface; use Magento\InventoryCatalogApi\Model\IsSingleSourceModeInterface; use Magento\InventoryImportExport\Plugin\Import\SourceItemImporter; +use Magento\InventoryIndexer\Indexer\CompositeProductsIndexer; use Magento\InventoryIndexer\Indexer\SourceItem\SourceItemIndexer; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -71,6 +72,11 @@ class SourceItemImporterTest extends TestCase */ private $isSingleSourceModeMock; + /** + * @var CompositeProductsIndexer|MockObject + */ + private $compositeProductsIndexerMock; + /** * @var SkuStorage|MockObject */ @@ -81,26 +87,14 @@ class SourceItemImporterTest extends TestCase */ protected function setUp(): void { - $this->sourceItemsSaveMock = $this->getMockBuilder(SourceItemsSaveInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $this->sourceItemsSaveMock = $this->createMock(SourceItemsSaveInterface::class); $this->sourceItemFactoryMock = $this->createMock(SourceItemInterfaceFactory::class); - $this->defaultSourceMock = $this->getMockBuilder(DefaultSourceProviderInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->sourceItemResourceModelMock = $this->getMockBuilder(SourceItem::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->stockItemProcessorMock = $this->getMockBuilder(StockItemProcessorInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->sourceItemMock = $this->getMockBuilder(SourceItemInterface::class) - ->disableOriginalConstructor() - ->getMock(); - + $this->defaultSourceMock = $this->createMock(DefaultSourceProviderInterface::class); + $this->sourceItemResourceModelMock = $this->createMock(SourceItem::class); + $this->stockItemProcessorMock = $this->createMock(StockItemProcessorInterface::class); + $this->sourceItemMock = $this->createMock(SourceItemInterface::class); $this->isSingleSourceModeMock = $this->createMock(IsSingleSourceModeInterface::class); + $this->compositeProductsIndexerMock = $this->createMock(CompositeProductsIndexer::class); $this->skuStorageMock = $this->createMock(SkuStorage::class); @@ -112,6 +106,7 @@ protected function setUp(): void $this->skuStorageMock, $this->sourceItemResourceModelMock, $this->createMock(SourceItemIndexer::class), + $this->compositeProductsIndexerMock, ); } @@ -182,6 +177,7 @@ public function testAfterImportForMultipleSource( $this->sourceItemsSaveMock->expects($this->once())->method('execute')->with([$this->sourceItemMock]) ->willReturnSelf(); } + $this->compositeProductsIndexerMock->expects($this->once())->method('reindexList')->with([$sku]); $this->plugin->afterProcess($this->stockItemProcessorMock, '', $stockData, []); } diff --git a/InventoryInStorePickupAdminUi/Test/Mftf/ActionGroup/AdminPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup.xml b/InventoryInStorePickupAdminUi/Test/Mftf/ActionGroup/AdminPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup.xml new file mode 100644 index 00000000000..fa57b6264e4 --- /dev/null +++ b/InventoryInStorePickupAdminUi/Test/Mftf/ActionGroup/AdminPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup"> + <annotations> + <description>Fill guest customer billing address for store pickup shipping method with provided address in admin.</description> + </annotations> + <arguments> + <argument name="customerVar"/> + <argument name="customerAddressVar"/> + </arguments> + + <!-- The billing form is already in edit mode, so just wait for it to be visible --> + <waitForElementVisible selector="{{CheckoutPaymentSection.guestFirstName}}" stepKey="waitForBillingFormVisible"/> + + <!-- Fill the form fields --> + <fillField selector="{{CheckoutPaymentSection.guestFirstName}}" userInput="{{customerVar.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutPaymentSection.guestLastName}}" userInput="{{customerVar.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutPaymentSection.guestStreet}}" userInput="{{customerAddressVar.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutPaymentSection.guestCity}}" userInput="{{customerAddressVar.city}}" stepKey="enterCity"/> + <selectOption selector="{{CheckoutPaymentSection.guestRegion}}" userInput="{{customerAddressVar.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutPaymentSection.guestPostcode}}" userInput="{{customerAddressVar.postcode}}" stepKey="enterPostcode"/> + <fillField selector="{{CheckoutPaymentSection.guestTelephone}}" userInput="{{customerAddressVar.telephone}}" stepKey="enterTelephone"/> + + <!-- Save the address --> + <click selector="{{CheckoutPaymentSection.update}}" stepKey="updateAddress"/> + <waitForLoadingMaskToDisappear stepKey="waitForAddressUpdate"/> + </actionGroup> +</actionGroups> + diff --git a/InventoryInStorePickupAdminUi/Test/Mftf/Section/AdminEditSourcePickupLocationSection.xml b/InventoryInStorePickupAdminUi/Test/Mftf/Section/AdminEditSourcePickupLocationSection.xml index d4bad407eaf..555b9708e3d 100644 --- a/InventoryInStorePickupAdminUi/Test/Mftf/Section/AdminEditSourcePickupLocationSection.xml +++ b/InventoryInStorePickupAdminUi/Test/Mftf/Section/AdminEditSourcePickupLocationSection.xml @@ -9,6 +9,6 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditSourcePickupLocationSection"> <element name="tabName" type="text" selector="//strong[contains(@class, 'collapsible-title')]/descendant::span[contains(text(), 'Pickup Location')]"/> - <element name="InsertImageIcon" type="button" selector="button[title='Insert/edit image']" /> + <element name="InsertImageIcon" type="button" selector="button[aria-label='Insert/edit image']" /> </section> </sections> diff --git a/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderBundleProductTest.xml b/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderBundleProductTest.xml index b9d0ab73e78..69c92246796 100644 --- a/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderBundleProductTest.xml +++ b/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderBundleProductTest.xml @@ -114,7 +114,7 @@ <argument name="sourceName" value="$culverSource.source[name]$"/> </actionGroup> <actionGroup ref="StorefrontPickInStoreNavigateToPaymentActionGroup" stepKey="navigateToPaymentStep"/> - <actionGroup ref="StorefrontPickInStoreGuestCustomerFillBillingAddressActionGroup" stepKey="fillAddress"> + <actionGroup ref="AdminPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup" stepKey="fillAddress"> <argument name="customerVar" value="Simple_US_Customer"/> <argument name="customerAddressVar" value="US_Address_TX"/> </actionGroup> diff --git a/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderConfigurableProductTest.xml b/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderConfigurableProductTest.xml index b8774508aa2..8c31fdf01e7 100644 --- a/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderConfigurableProductTest.xml +++ b/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderConfigurableProductTest.xml @@ -114,7 +114,7 @@ <argument name="sourceName" value="$culverSource.source[name]$"/> </actionGroup> <actionGroup ref="StorefrontPickInStoreNavigateToPaymentActionGroup" stepKey="navigateToPaymentStep"/> - <actionGroup ref="StorefrontPickInStoreGuestCustomerFillBillingAddressActionGroup" stepKey="fillAddress"> + <actionGroup ref="AdminPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup" stepKey="fillAddress"> <argument name="customerVar" value="Simple_US_Customer"/> <argument name="customerAddressVar" value="US_Address_TX"/> </actionGroup> diff --git a/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderGroupedProductTest.xml b/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderGroupedProductTest.xml index 654aa21c8d3..8348cbdfd63 100644 --- a/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderGroupedProductTest.xml +++ b/InventoryInStorePickupAdminUi/Test/Mftf/Test/AdminNotifyCustomerForPickupOrderGroupedProductTest.xml @@ -115,7 +115,7 @@ <argument name="sourceName" value="$culverSource.source[name]$"/> </actionGroup> <actionGroup ref="StorefrontPickInStoreNavigateToPaymentActionGroup" stepKey="navigateToPaymentStep"/> - <actionGroup ref="StorefrontPickInStoreGuestCustomerFillBillingAddressActionGroup" stepKey="fillAddress"> + <actionGroup ref="AdminPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup" stepKey="fillAddress"> <argument name="customerVar" value="Simple_US_Customer"/> <argument name="customerAddressVar" value="US_Address_TX"/> </actionGroup> diff --git a/InventoryInStorePickupFrontend/Test/Mftf/ActionGroup/StorefrontPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup.xml b/InventoryInStorePickupFrontend/Test/Mftf/ActionGroup/StorefrontPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup.xml new file mode 100644 index 00000000000..5a978e28eac --- /dev/null +++ b/InventoryInStorePickupFrontend/Test/Mftf/ActionGroup/StorefrontPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup"> + <annotations> + <description>Fill guest customer billing address for store pickup shipping method with provided address.</description> + </annotations> + <arguments> + <argument name="customerVar"/> + <argument name="customerAddressVar"/> + </arguments> + + <!-- The billing form is already in edit mode, so just wait for it to be visible --> + <waitForElementVisible selector="{{CheckoutPaymentSection.guestFirstName}}" stepKey="waitForBillingFormVisible"/> + + <!-- Fill the form fields --> + <fillField selector="{{CheckoutPaymentSection.guestFirstName}}" userInput="{{customerVar.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutPaymentSection.guestLastName}}" userInput="{{customerVar.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutPaymentSection.guestStreet}}" userInput="{{customerAddressVar.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutPaymentSection.guestCity}}" userInput="{{customerAddressVar.city}}" stepKey="enterCity"/> + <selectOption selector="{{CheckoutPaymentSection.guestRegion}}" userInput="{{customerAddressVar.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutPaymentSection.guestPostcode}}" userInput="{{customerAddressVar.postcode}}" stepKey="enterPostcode"/> + <fillField selector="{{CheckoutPaymentSection.guestTelephone}}" userInput="{{customerAddressVar.telephone}}" stepKey="enterTelephone"/> + + <!-- Save the address --> + <click selector="{{CheckoutPaymentSection.update}}" stepKey="updateAddress"/> + <waitForLoadingMaskToDisappear stepKey="waitForAddressUpdate"/> + </actionGroup> +</actionGroups> diff --git a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontBillingAddressFilledAutomaticallyWithSimpleProductCustomStockTest.xml b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontBillingAddressFilledAutomaticallyWithSimpleProductCustomStockTest.xml index 8df4e554d66..1f8808f414a 100644 --- a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontBillingAddressFilledAutomaticallyWithSimpleProductCustomStockTest.xml +++ b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontBillingAddressFilledAutomaticallyWithSimpleProductCustomStockTest.xml @@ -103,6 +103,8 @@ <argument name="sourceName" value="$culverSource.source[name]$"/> </actionGroup> <actionGroup ref="StorefrontPickInStoreNavigateToPaymentActionGroup" stepKey="navigateToPaymentStep"/> - <actionGroup ref="StorefrontAssertPaymentInformationActionGroup" stepKey="verifyPaymentInformation"/> + <actionGroup ref="StorefrontAssertPaymentInformationActionGroup" stepKey="verifyPaymentInformation"> + <argument name="address" value="{{US_Address_TX.street[0]}}"/> + </actionGroup> </test> </tests> diff --git a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontGuestBillingAddressPreselectedTest.xml b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontGuestBillingAddressPreselectedTest.xml index 954d4d45f9f..63782548267 100644 --- a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontGuestBillingAddressPreselectedTest.xml +++ b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontGuestBillingAddressPreselectedTest.xml @@ -19,6 +19,9 @@ <group value="msi"/> <group value="store_pickup"/> <group value="pr_exclude"/> + <skip> + <issueId value="AC-15491">Skipped because of AC bug</issueId> + </skip> </annotations> <before> <!--Set Distance Provider for Distance Based SSA to offline--> diff --git a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderWithSimpleProductCustomStockCustomWebsiteTest.xml b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderWithSimpleProductCustomStockCustomWebsiteTest.xml index 1c983db04a7..056d0034be5 100644 --- a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderWithSimpleProductCustomStockCustomWebsiteTest.xml +++ b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontLoggedInCustomerCreateOrderWithSimpleProductCustomStockCustomWebsiteTest.xml @@ -17,7 +17,6 @@ <group value="msi"/> <group value="store_pickup"/> </annotations> - <before> <!--Add store code to url.--> <magentoCLI command="config:set {{StorefrontEnableAddStoreCodeToUrls.path}} {{StorefrontEnableAddStoreCodeToUrls.value}}" stepKey="addStoreCodeToUrlEnable"/> @@ -108,12 +107,15 @@ </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableAllSources"/> + <!--Delete customer created during test execution--> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomerFromGrid"> + <argument name="customerEmail" value="Simple_US_Customer.email"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value=""/> </actionGroup> </after> - <!--Register customer.--> <actionGroup ref="RegisterCustomOnStorefrontActionGroup" stepKey="registerCustomer"> <argument name="Customer" value="Simple_US_Customer"/> @@ -132,7 +134,7 @@ <argument name="sourceName" value="$culverSource.source[name]$"/> </actionGroup> <actionGroup ref="StorefrontPickInStoreNavigateToPaymentActionGroup" stepKey="navigateToPaymentStep"/> - <actionGroup ref="StorefrontPickInStoreGuestCustomerFillBillingAddressActionGroup" stepKey="fillAddress"> + <actionGroup ref="StorefrontPickInStoreGuestCustomerFillBillingAddressWithParamsActionGroup" stepKey="fillAddress"> <argument name="customerVar" value="Simple_US_Customer"/> <argument name="customerAddressVar" value="US_Address_TX"/> </actionGroup> diff --git a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontPickUpSourceInfoSimpleProductCustomStockTest.xml b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontPickUpSourceInfoSimpleProductCustomStockTest.xml index b207e8d2b89..d5339548e29 100644 --- a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontPickUpSourceInfoSimpleProductCustomStockTest.xml +++ b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontPickUpSourceInfoSimpleProductCustomStockTest.xml @@ -16,7 +16,6 @@ <severity value="MAJOR"/> <group value="msi"/> <group value="store_pickup"/> - <group value="pr_exclude"/> </annotations> <before> diff --git a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontReviewAndPaymentPageWithSimpleProductCustomStockTest.xml b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontReviewAndPaymentPageWithSimpleProductCustomStockTest.xml index a743c32fa86..eac9ae32656 100644 --- a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontReviewAndPaymentPageWithSimpleProductCustomStockTest.xml +++ b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontReviewAndPaymentPageWithSimpleProductCustomStockTest.xml @@ -17,7 +17,6 @@ <group value="msi"/> <group value="store_pickup"/> </annotations> - <before> <!--Set Distance Provider for Distance Based SSA to offline--> <magentoCLI command="config:set {{OfflineDistanceProviderForDistanceBasedSSA.path}} {{OfflineDistanceProviderForDistanceBasedSSA.value}}" stepKey="setDistanceProviderToOffline"/> @@ -75,12 +74,15 @@ </actionGroup> <deleteData createDataKey="stock" stepKey="deleteStock"/> <actionGroup ref="DisableAllSourcesActionGroup" stepKey="disableAllSources"/> + <!--Delete customer created during test execution--> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomerFromGrid"> + <argument name="customerEmail" value="Simple_US_Customer.email"/> + </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> <argument name="indices" value=""/> </actionGroup> </after> - <!--Register customer and add simple product to cart.--> <actionGroup ref="RegisterCustomOnStorefrontActionGroup" stepKey="registerCustomer"> <argument name="Customer" value="Simple_US_Customer"/> diff --git a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontSummaryShippingSectionWithSimpleProductCustomStockTest.xml b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontSummaryShippingSectionWithSimpleProductCustomStockTest.xml index e014f5efcf2..6473299e0cf 100644 --- a/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontSummaryShippingSectionWithSimpleProductCustomStockTest.xml +++ b/InventoryInStorePickupFrontend/Test/Mftf/Test/StorefrontSummaryShippingSectionWithSimpleProductCustomStockTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <group value="msi"/> <group value="store_pickup"/> + <skip> + <issueId value="AC-15491">Skipped because of AC bug</issueId> + </skip> </annotations> <before> diff --git a/InventoryInStorePickupFrontend/view/frontend/web/js/model/pickup-locations-service.js b/InventoryInStorePickupFrontend/view/frontend/web/js/model/pickup-locations-service.js index 57a3df18556..2984a502c0c 100644 --- a/InventoryInStorePickupFrontend/view/frontend/web/js/model/pickup-locations-service.js +++ b/InventoryInStorePickupFrontend/view/frontend/web/js/model/pickup-locations-service.js @@ -12,6 +12,9 @@ define([ 'Magento_Checkout/js/checkout-data', 'Magento_Checkout/js/model/address-converter', 'Magento_Checkout/js/action/select-shipping-address', + 'Magento_Checkout/js/action/select-billing-address', + 'Magento_Checkout/js/model/checkout-data-resolver', + 'Magento_Checkout/js/model/quote', 'underscore', 'mage/translate', 'mage/url', @@ -25,6 +28,9 @@ define([ checkoutData, addressConverter, selectShippingAddressAction, + selectBillingAddressAction, + checkoutDataResolver, + quote, _, $t, url, @@ -104,29 +110,30 @@ define([ }, /** - * Select location for sipping. + * Select location for shipping. * * @param {Object} location * @param {Boolean} [persist=true] * @returns void */ selectForShipping: function (location, persist) { - var address = $.extend( - {}, - addressConverter.formAddressDataToQuoteAddress({ - firstname: location.name, - lastname: 'Store', - street: location.street, - city: location.city, - postcode: location.postcode, - 'country_id': location['country_id'], - telephone: location.telephone, - 'region_id': location['region_id'], - 'save_in_address_book': 0, - 'extension_attributes': { - 'pickup_location_code': location['pickup_location_code'] - } - })); + var billingAddress = quote.billingAddress(), + address = $.extend( + {}, + addressConverter.formAddressDataToQuoteAddress({ + firstname: location.name, + lastname: 'Store', + street: location.street, + city: location.city, + postcode: location.postcode, + 'country_id': location['country_id'], + telephone: location.telephone, + 'region_id': location['region_id'], + 'save_in_address_book': 0, + 'extension_attributes': { + 'pickup_location_code': location['pickup_location_code'] + } + })); address = pickupAddressConverter.formatAddressToPickupAddress(address); this.selectedLocation(location); @@ -137,6 +144,10 @@ define([ addressConverter.quoteAddressToFormAddressData(address) ); } + if (!billingAddress) { + quote.billingAddress(null); + checkoutDataResolver.resolveBillingAddress(); + } }, /** @@ -191,6 +202,43 @@ define([ return regions && regions[regionId] ? regions[regionId].name : ''; }, + /** + * Check if billing address is incomplete (missing required fields) + * + * @param {Object} billingAddress + * @returns {Boolean} + */ + isBillingAddressIncomplete: function (billingAddress) { + var field, + value, + counter, + requiredFields = [ + 'firstname', + 'lastname', + 'street', + 'city', + 'postcode', + 'telephone', + 'regionId', + 'countryId' + ]; + + if (!billingAddress) { + return true; + } + for (counter = 0; counter < requiredFields.length; counter++) { + field = requiredFields[counter]; + value = billingAddress[field]; + if (field === 'street' && (!value || !Array.isArray(value) || value.length === 0 || !value[0])) { + return true; + } + if (field !== 'street' && (!value || value === '' || value === null || value === undefined)) { + return true; + } + } + return false; + }, + /** * Process response errors. * @@ -210,7 +258,7 @@ define([ try { error = JSON.parse(response.responseText); - } catch (exception) { + } catch (exception) { // eslint-disable-line no-unused-vars error = $t( 'Something went wrong with your request. Please try again later.' ); diff --git a/InventoryInStorePickupQuote/Model/ExtractQuoteAddressShippingAddressData.php b/InventoryInStorePickupQuote/Model/ExtractQuoteAddressShippingAddressData.php index c4216d08faa..543f332e8dc 100644 --- a/InventoryInStorePickupQuote/Model/ExtractQuoteAddressShippingAddressData.php +++ b/InventoryInStorePickupQuote/Model/ExtractQuoteAddressShippingAddressData.php @@ -9,6 +9,7 @@ use Magento\Framework\DataObject\Copy; use Magento\Quote\Api\Data\AddressInterface; +use Magento\InventoryInStorePickupShippingApi\Model\Carrier\InStorePickup; /** * Extract quote address details according to fieldset config. @@ -42,13 +43,13 @@ public function execute(AddressInterface $address): array 'shipping_address_data', $address ); - - // TODO: temporary solution to avoid issue with config merge. - $data['customer_address_id'] = $address->getCustomerAddressId(); - if (isset($data[AddressInterface::KEY_STREET]) && is_array($data[AddressInterface::KEY_STREET])) { $data[AddressInterface::KEY_STREET] = implode("\n", $data[AddressInterface::KEY_STREET]); } + $data[AddressInterface::SAME_AS_BILLING] = false; + $data[AddressInterface::SAVE_IN_ADDRESS_BOOK] = false; + $data[AddressInterface::CUSTOMER_ADDRESS_ID] = null; + $data['shipping_method'] = InStorePickup::DELIVERY_METHOD; return $data; } diff --git a/InventoryInStorePickupQuote/Plugin/Checkout/ShippingInformationManagementPlugin.php b/InventoryInStorePickupQuote/Plugin/Checkout/ShippingInformationManagementPlugin.php new file mode 100644 index 00000000000..50ed793153e --- /dev/null +++ b/InventoryInStorePickupQuote/Plugin/Checkout/ShippingInformationManagementPlugin.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryInStorePickupQuote\Plugin\Checkout; + +use Magento\Checkout\Api\Data\ShippingInformationInterface; +use Magento\Checkout\Model\ShippingInformationManagement; +use Magento\Quote\Api\Data\AddressInterface; + +/** + * Plugin to handle store pickup address logic in ShippingInformationManagement + */ +class ShippingInformationManagementPlugin +{ + /** + * Before plugin for saveAddressInformation to handle store pickup billing address logic + * + * @param ShippingInformationManagement $subject + * @param int $cartId + * @param ShippingInformationInterface $addressInformation + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeSaveAddressInformation( + ShippingInformationManagement $subject, + int $cartId, + ShippingInformationInterface $addressInformation + ): array { + $address = $addressInformation->getShippingAddress(); + $billingAddress = $addressInformation->getBillingAddress(); + + if (!$this->isPickupStoreShipping($address) && $this->isBillingAddressCompletelyNull($billingAddress)) { + $addressInformation->setBillingAddress($address); + } + + return [$cartId, $addressInformation]; + } + + /** + * Check if shipping method is in-store pickup + * + * @param AddressInterface|null $shippingAddress + * @return bool + */ + private function isPickupStoreShipping(?AddressInterface $shippingAddress): bool + { + if (!$shippingAddress) { + return false; + } + $extensionAttributes = $shippingAddress->getExtensionAttributes(); + if ($extensionAttributes && $extensionAttributes->getPickupLocationCode()) { + return true; + } + return false; + } + + /** + * Check if billing address is completely null (not set at all) + * + * @param AddressInterface|null $billingAddress + * @return bool + */ + private function isBillingAddressCompletelyNull(?AddressInterface $billingAddress): bool + { + return $billingAddress === null; + } +} diff --git a/InventoryInStorePickupQuote/Test/Unit/Model/ExtractQuoteAddressShippingAddressDataTest.php b/InventoryInStorePickupQuote/Test/Unit/Model/ExtractQuoteAddressShippingAddressDataTest.php new file mode 100644 index 00000000000..c9dc58d820e --- /dev/null +++ b/InventoryInStorePickupQuote/Test/Unit/Model/ExtractQuoteAddressShippingAddressDataTest.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryInStorePickupQuote\Test\Unit\Model; + +use Magento\Framework\DataObject\Copy; +use Magento\InventoryInStorePickupQuote\Model\ExtractQuoteAddressShippingAddressData; +use Magento\InventoryInStorePickupShippingApi\Model\Carrier\InStorePickup; +use Magento\Quote\Api\Data\AddressInterface; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; + +/** + * Unit test for ExtractQuoteAddressShippingAddressData + */ +class ExtractQuoteAddressShippingAddressDataTest extends TestCase +{ + /** + * @var ExtractQuoteAddressShippingAddressData + */ + private $extractor; + + /** + * @var Copy|MockObject + */ + private $objectCopyServiceMock; + + /** + * @var AddressInterface|MockObject + */ + private $addressMock; + + protected function setUp(): void + { + $this->objectCopyServiceMock = $this->createMock(Copy::class); + $this->addressMock = $this->createMock(AddressInterface::class); + $this->extractor = new ExtractQuoteAddressShippingAddressData( + $this->objectCopyServiceMock + ); + } + + /** + * Test execute method to verify specific data values + */ + public function testExecuteWithValidAddressData(): void + { + $addressData = [ + AddressInterface::KEY_STREET => ['123 Main St', 'Apt 4B'], + 'some_field' => 'some_value' + ]; + $this->objectCopyServiceMock + ->method('getDataFromFieldset') + ->with('sales_convert_quote_address', 'shipping_address_data', $this->addressMock) + ->willReturn($addressData); + $result = $this->extractor->execute($this->addressMock); + $expectedData = [ + AddressInterface::KEY_STREET => "123 Main St\nApt 4B", + 'some_field' => 'some_value', + AddressInterface::SAME_AS_BILLING => false, + AddressInterface::SAVE_IN_ADDRESS_BOOK => false, + AddressInterface::CUSTOMER_ADDRESS_ID => null, + 'shipping_method' => InStorePickup::DELIVERY_METHOD + ]; + $this->assertEquals($expectedData, $result); + } + + /** + * Test execute method with non-array street field + */ + public function testExecuteWithNonArrayStreetField(): void + { + $addressData = [ + AddressInterface::KEY_STREET => '123 Main St', + 'some_field' => 'some_value' + ]; + $this->objectCopyServiceMock + ->method('getDataFromFieldset') + ->with('sales_convert_quote_address', 'shipping_address_data', $this->addressMock) + ->willReturn($addressData); + $result = $this->extractor->execute($this->addressMock); + $expectedData = [ + AddressInterface::KEY_STREET => '123 Main St', + 'some_field' => 'some_value', + AddressInterface::SAME_AS_BILLING => false, + AddressInterface::SAVE_IN_ADDRESS_BOOK => false, + AddressInterface::CUSTOMER_ADDRESS_ID => null, + 'shipping_method' => InStorePickup::DELIVERY_METHOD + ]; + $this->assertEquals($expectedData, $result); + } +} diff --git a/InventoryInStorePickupQuote/Test/Unit/Plugin/Checkout/ShippingInformationManagementPluginTest.php b/InventoryInStorePickupQuote/Test/Unit/Plugin/Checkout/ShippingInformationManagementPluginTest.php new file mode 100644 index 00000000000..e1a6b638d03 --- /dev/null +++ b/InventoryInStorePickupQuote/Test/Unit/Plugin/Checkout/ShippingInformationManagementPluginTest.php @@ -0,0 +1,182 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryInStorePickupQuote\Test\Unit\Plugin\Checkout; + +use Magento\Checkout\Api\Data\ShippingInformationInterface; +use Magento\Checkout\Model\ShippingInformationManagement; +use Magento\InventoryInStorePickupQuote\Plugin\Checkout\ShippingInformationManagementPlugin; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\AddressExtensionInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Unit test for ShippingInformationManagementPlugin. + */ +class ShippingInformationManagementPluginTest extends TestCase +{ + /** + * Test subject. + * + * @var ShippingInformationManagementPlugin + */ + private $plugin; + + /** + * @var ShippingInformationManagement|MockObject + */ + private $subject; + + /** + * @var ShippingInformationInterface|MockObject + */ + private $addressInformation; + + /** + * @var AddressInterface|MockObject + */ + private $shippingAddress; + + /** + * @var AddressInterface|MockObject + */ + private $billingAddress; + + /** + * @var AddressInterface|MockObject + */ + private $extensionAttributes; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $this->subject = $this->getMockBuilder(ShippingInformationManagement::class) + ->disableOriginalConstructor() + ->getMock(); + $this->addressInformation = $this->getMockBuilder(ShippingInformationInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->shippingAddress = $this->getMockBuilder(AddressInterface::class) + ->onlyMethods(['getExtensionAttributes']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->billingAddress = $this->getMockBuilder(AddressInterface::class) + ->onlyMethods( + [ + 'getFirstname', + 'getLastname', + 'getStreet', + 'getCity', + 'getPostcode', + 'getTelephone', + 'getRegionId', + 'getCountryId' + ] + ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->extensionAttributes = $this->getMockBuilder(AddressExtensionInterface::class) + ->addMethods(['getPickupLocationCode']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->plugin = new ShippingInformationManagementPlugin(); + } + + /** + * Test beforeSaveAddressInformation when it's pickup store with incomplete billing + * + * @return void + */ + public function testBeforeSaveAddressInformationPickupStoreIncompleteBilling(): void + { + $cartId = 123; + $pickupLocationCode = 'store_001'; + $this->shippingAddress->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->extensionAttributes); + $this->extensionAttributes->expects($this->once()) + ->method('getPickupLocationCode') + ->willReturn($pickupLocationCode); + $this->addressInformation->expects($this->once()) + ->method('getShippingAddress') + ->willReturn($this->shippingAddress); + $this->addressInformation->expects($this->once()) + ->method('getBillingAddress') + ->willReturn($this->billingAddress); + $this->addressInformation->expects($this->never())->method('setBillingAddress'); + $result = $this->plugin->beforeSaveAddressInformation( + $this->subject, + $cartId, + $this->addressInformation + ); + $this->assertEquals([$cartId, $this->addressInformation], $result); + } + + /** + * Test beforeSaveAddressInformation when it's pickup store with complete billing + * + * @return void + */ + public function testBeforeSaveAddressInformationPickupStoreCompleteBilling(): void + { + $cartId = 123; + $pickupLocationCode = 'store_001'; + $this->shippingAddress->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->extensionAttributes); + $this->extensionAttributes->expects($this->once()) + ->method('getPickupLocationCode') + ->willReturn($pickupLocationCode); + $this->addressInformation->expects($this->once()) + ->method('getShippingAddress') + ->willReturn($this->shippingAddress); + $this->addressInformation->expects($this->once()) + ->method('getBillingAddress') + ->willReturn($this->billingAddress); + $this->addressInformation->expects($this->never())->method('setBillingAddress'); + $result = $this->plugin->beforeSaveAddressInformation( + $this->subject, + $cartId, + $this->addressInformation + ); + $this->assertEquals([$cartId, $this->addressInformation], $result); + } + + /** + * Test beforeSaveAddressInformation when it's not pickup store and billing is null + * + * @return void + */ + public function testBeforeSaveAddressInformationNotPickupStore(): void + { + $cartId = 123; + $this->shippingAddress->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->extensionAttributes); + $this->extensionAttributes->expects($this->once()) + ->method('getPickupLocationCode') + ->willReturn(null); + $this->addressInformation->expects($this->once()) + ->method('getShippingAddress') + ->willReturn($this->shippingAddress); + $this->addressInformation->expects($this->once()) + ->method('getBillingAddress') + ->willReturn(null); + $this->addressInformation->expects($this->once()) + ->method('setBillingAddress') + ->with($this->shippingAddress); + $result = $this->plugin->beforeSaveAddressInformation( + $this->subject, + $cartId, + $this->addressInformation + ); + $this->assertEquals([$cartId, $this->addressInformation], $result); + } +} diff --git a/InventoryInStorePickupQuote/composer.json b/InventoryInStorePickupQuote/composer.json index b5b7501b69b..c0fc039133e 100644 --- a/InventoryInStorePickupQuote/composer.json +++ b/InventoryInStorePickupQuote/composer.json @@ -10,7 +10,8 @@ "magento/module-inventory-in-store-pickup-shipping-api": "*", "magento/module-store": "*", "magento/module-customer": "*", - "magento/module-inventory-sales-api": "*" + "magento/module-inventory-sales-api": "*", + "magento/module-checkout": "*" }, "type": "magento2-module", "license": [ diff --git a/InventoryInStorePickupQuote/etc/di.xml b/InventoryInStorePickupQuote/etc/di.xml index 7677508c62e..50336d6ab90 100644 --- a/InventoryInStorePickupQuote/etc/di.xml +++ b/InventoryInStorePickupQuote/etc/di.xml @@ -37,4 +37,7 @@ <type name="Magento\Quote\Model\Quote"> <plugin name="inventory_in_store_pickup_quote_replace_shipping_address_on_assign_customer" sortOrder="20" type="Magento\InventoryInStorePickupQuote\Plugin\Quote\ReplaceShippingAddressWithPickupLocationAddressOnAssignCustomer" /> </type> + <type name="Magento\Checkout\Model\ShippingInformationManagement"> + <plugin name="inventory_in_store_pickup_quote_shipping_information_management" sortOrder="20" type="Magento\InventoryInStorePickupQuote\Plugin\Checkout\ShippingInformationManagementPlugin" /> + </type> </config> diff --git a/InventoryIndexer/Indexer/CompositeProductsIndexer.php b/InventoryIndexer/Indexer/CompositeProductsIndexer.php new file mode 100644 index 00000000000..dd6240576c9 --- /dev/null +++ b/InventoryIndexer/Indexer/CompositeProductsIndexer.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryIndexer\Indexer; + +use Magento\InventoryApi\Model\GetStockIdsBySkusInterface; +use Magento\InventoryCatalogApi\Model\CompositeProductTypesProviderInterface; +use Magento\InventoryCatalogApi\Model\GetChildrenSkusOfParentSkusInterface; +use Magento\InventoryCatalogApi\Model\GetProductTypesBySkusInterface; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; + +class CompositeProductsIndexer +{ + /** + * @param CompositeProductTypesProviderInterface $compositeProductTypesProvider + * @param GetProductTypesBySkusInterface $getProductTypesBySkus + * @param GetChildrenSkusOfParentSkusInterface $getChildrenSkusOfParentSkus + * @param GetStockIdsBySkusInterface $getStockIdsBySkus + * @param SkuListInStockFactory $skuListInStockFactory + * @param SkuListsProcessor $skuListsProcessor + */ + public function __construct( + private readonly CompositeProductTypesProviderInterface $compositeProductTypesProvider, + private readonly GetProductTypesBySkusInterface $getProductTypesBySkus, + private readonly GetChildrenSkusOfParentSkusInterface $getChildrenSkusOfParentSkus, + private readonly GetStockIdsBySkusInterface $getStockIdsBySkus, + private readonly SkuListInStockFactory $skuListInStockFactory, + private readonly SkuListsProcessor $skuListsProcessor, + ) { + } + + /** + * Reindex composite products present in the provided SKU list for stocks related to their child products. + * + * @param string[] $skus + * @return void + */ + public function reindexList(array $skus): void + { + if (!$skus) { + return; + } + + $productTypesBySkus = $this->getProductTypesBySkus->execute($skus); + $productSkusByTypes = array_fill_keys(array_unique(array_values($productTypesBySkus)), []); + foreach ($productTypesBySkus as $sku => $type) { + $productSkusByTypes[$type][] = (string) $sku; + } + $compositeTypes = $this->compositeProductTypesProvider->execute(); + $compositeSkusByTypes = array_intersect_key($productSkusByTypes, array_flip($compositeTypes)); + $compositeSkus = array_merge([], ...array_values($compositeSkusByTypes)); + if (!$compositeSkus) { + return; + } + + $parentStocks = []; + $childrenSkusOfParentSkus = $this->getChildrenSkusOfParentSkus->execute($compositeSkus); + foreach ($childrenSkusOfParentSkus as $parentSku => $childrenSkus) { + if (!$childrenSkus) { + continue; + } + + $stockIds = $this->getStockIdsBySkus->execute($childrenSkus); + foreach ($stockIds as $stockId) { + $parentStocks[$stockId][] = (string) $parentSku; + } + } + + $skuListInStockList = []; + foreach ($parentStocks as $stockId => $parentSkus) { + $skuListInStockList[] = $this->skuListInStockFactory->create( + ['stockId' => $stockId, 'skuList' => $parentSkus] + ); + } + $this->skuListsProcessor->reindexList($skuListInStockList); + } +} diff --git a/InventoryIndexer/Indexer/SiblingProductsProviderInterface.php b/InventoryIndexer/Indexer/SiblingProductsProviderInterface.php index 986d111eaa2..64cbe0d5e1e 100644 --- a/InventoryIndexer/Indexer/SiblingProductsProviderInterface.php +++ b/InventoryIndexer/Indexer/SiblingProductsProviderInterface.php @@ -7,8 +7,6 @@ namespace Magento\InventoryIndexer\Indexer; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; - interface SiblingProductsProviderInterface { /** @@ -18,13 +16,4 @@ interface SiblingProductsProviderInterface * @return string[] */ public function getSkus(array $skus): array; - - /** - * Retrieve sibling product data based on their SKUs. - * - * @param IndexName $indexName - * @param array $skuList - * @return array - */ - public function getData(IndexName $indexName, array $skuList = []): array; } diff --git a/InventoryIndexer/Indexer/SiblingProductsProvidersPool.php b/InventoryIndexer/Indexer/SiblingProductsProvidersPool.php new file mode 100644 index 00000000000..82eb33a3523 --- /dev/null +++ b/InventoryIndexer/Indexer/SiblingProductsProvidersPool.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryIndexer\Indexer; + +class SiblingProductsProvidersPool +{ + /** + * @param SiblingProductsProviderInterface[] $siblingProductsProviders + */ + public function __construct( + private readonly array $siblingProductsProviders = [], + ) { + (fn (SiblingProductsProviderInterface ...$siblingProductsProviders) => $siblingProductsProviders)( + ...$this->siblingProductsProviders + ); + } + + /** + * Get sibling SKUs grouped by product type. + * + * @param string[] $skus + * @return array<string, string[]> + */ + public function getSiblingsGroupedByType(array $skus): array + { + if (!$skus) { + return []; + } + + $result = []; + foreach ($this->siblingProductsProviders as $productType => $siblingProductsProvider) { + $siblingSkus = $siblingProductsProvider->getSkus($skus); + if (!$siblingSkus) { + continue; + } + $result[$productType] = $siblingSkus; + } + + return $result; + } +} diff --git a/InventoryIndexer/Indexer/SiblingSelectBuilderInterface.php b/InventoryIndexer/Indexer/SiblingSelectBuilderInterface.php index 40985c74dac..531303366ad 100644 --- a/InventoryIndexer/Indexer/SiblingSelectBuilderInterface.php +++ b/InventoryIndexer/Indexer/SiblingSelectBuilderInterface.php @@ -8,16 +8,17 @@ namespace Magento\InventoryIndexer\Indexer; use Magento\Framework\DB\Select; -use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; interface SiblingSelectBuilderInterface { /** * Prepare select for sibling products. * - * @param IndexName $indexName - * @param array $skuList + * @param int $stockId + * @param string[] $skuList + * @param IndexAlias $indexAlias * @return Select */ - public function getSelect(IndexName $indexName, array $skuList = []): Select; + public function getSelect(int $stockId, array $skuList = [], IndexAlias $indexAlias = IndexAlias::MAIN): Select; } diff --git a/InventoryIndexer/Indexer/SourceItem/GetSkuListInStock.php b/InventoryIndexer/Indexer/SourceItem/GetSkuListInStock.php index c49fa1be4d1..df144cf814e 100644 --- a/InventoryIndexer/Indexer/SourceItem/GetSkuListInStock.php +++ b/InventoryIndexer/Indexer/SourceItem/GetSkuListInStock.php @@ -94,9 +94,7 @@ private function getStockIdToSkuList(array $items): array $skuListInStockList = []; foreach ($items as $stockId => $skuList) { /** @var SkuListInStock $skuListInStock */ - $skuListInStock = $this->skuListInStockFactory->create(); - $skuListInStock->setStockId((int)$stockId); - $skuListInStock->setSkuList($skuList); + $skuListInStock = $this->skuListInStockFactory->create(['stockId' => $stockId, 'skuList' => $skuList]); $skuListInStockList[] = $skuListInStock; } return $skuListInStockList; diff --git a/InventoryIndexer/Indexer/SourceItem/SkuListInStock.php b/InventoryIndexer/Indexer/SourceItem/SkuListInStock.php index 072c021db1e..40d86d32d0c 100644 --- a/InventoryIndexer/Indexer/SourceItem/SkuListInStock.php +++ b/InventoryIndexer/Indexer/SourceItem/SkuListInStock.php @@ -13,14 +13,14 @@ class SkuListInStock { /** - * @var int - */ - private $stockId; - - /** - * @var array + * @param int $stockId + * @param array $skuList */ - private $skuList; + public function __construct( + private int $stockId, + private array $skuList = [], + ) { + } /** * Returns the stock ID associated with the current instance. diff --git a/InventoryIndexer/Indexer/Stock/DataProvider.php b/InventoryIndexer/Indexer/Stock/DataProvider.php new file mode 100644 index 00000000000..538e5fc3ae8 --- /dev/null +++ b/InventoryIndexer/Indexer/Stock/DataProvider.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryIndexer\Indexer\Stock; + +use Magento\Framework\App\ResourceConnection; +use Magento\InventoryIndexer\Indexer\SelectBuilder; +use Magento\InventoryIndexer\Indexer\SiblingSelectBuilderInterface; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; + +class DataProvider +{ + /** + * @param ResourceConnection $resourceConnection + * @param SelectBuilder $selectBuilder + * @param SiblingSelectBuilderInterface[] $siblingSelectBuilders + */ + public function __construct( + private readonly ResourceConnection $resourceConnection, + private readonly SelectBuilder $selectBuilder, + private readonly array $siblingSelectBuilders = [], + ) { + } + + /** + * Get data using default select builder. + * + * @param int $stockId + * @param string[] $skuList + * @return array<string, array{sku: string, quantity: float, is_salable: int}> + */ + public function getData(int $stockId, array $skuList = []): array + { + $select = $this->selectBuilder->getSelect($stockId, $skuList); + $data = $this->resourceConnection->getConnection()->fetchAll($select); + $data = array_column($data, null, 'sku'); + + return $data; + } + + /** + * Get data for sibling products of specified type. + * + * @param int $stockId + * @param string $productType + * @param string[] $skuList + * @param IndexAlias $indexAlias + * @return array<string, array{sku: string, quantity: float, is_salable: int}> + */ + public function getSiblingsData( + int $stockId, + string $productType, + array $skuList = [], + IndexAlias $indexAlias = IndexAlias::MAIN + ): array { + $siblingSelectBuilder = $this->siblingSelectBuilders[$productType]; + $select = $siblingSelectBuilder->getSelect($stockId, $skuList, $indexAlias); + $data = $this->resourceConnection->getConnection()->fetchAll($select); + $data = array_column($data, null, 'sku'); + + return $data; + } +} diff --git a/InventoryIndexer/Indexer/Stock/IndexDataFiller.php b/InventoryIndexer/Indexer/Stock/IndexDataFiller.php index 6896a5f8af6..6304e056b33 100644 --- a/InventoryIndexer/Indexer/Stock/IndexDataFiller.php +++ b/InventoryIndexer/Indexer/Stock/IndexDataFiller.php @@ -8,81 +8,96 @@ namespace Magento\InventoryIndexer\Indexer\Stock; use ArrayIterator; -use Magento\Framework\App\ResourceConnection; -use Magento\InventoryIndexer\Indexer\SelectBuilder; -use Magento\InventoryIndexer\Indexer\SiblingProductsProviderInterface; +use Magento\InventoryCatalogApi\Model\GetProductTypesBySkusInterface; +use Magento\InventoryConfigurationApi\Model\GetAllowedProductTypesForSourceItemManagementInterface; +use Magento\InventoryIndexer\Indexer\SiblingProductsProvidersPool; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStock; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexHandlerInterface; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; class IndexDataFiller { /** - * @param ResourceConnection $resourceConnection - * @param SelectBuilder $selectBuilder + * @param ReservationsIndexTable $reservationsIndexTable + * @param PrepareReservationsIndexData $prepareReservationsIndexData + * @param GetProductTypesBySkusInterface $getProductTypesBySkus + * @param GetAllowedProductTypesForSourceItemManagementInterface $getSourceItemManagementProductTypes + * @param DataProvider $dataProvider * @param IndexHandlerInterface $indexStructureHandler - * @param SiblingProductsProviderInterface[] $siblingProductsProviders + * @param SiblingProductsProvidersPool $siblingProductsProvidersPool */ public function __construct( - private readonly ResourceConnection $resourceConnection, - private readonly SelectBuilder $selectBuilder, + private readonly ReservationsIndexTable $reservationsIndexTable, + private readonly PrepareReservationsIndexData $prepareReservationsIndexData, + private readonly GetProductTypesBySkusInterface $getProductTypesBySkus, + private readonly GetAllowedProductTypesForSourceItemManagementInterface $getSourceItemManagementProductTypes, + private readonly DataProvider $dataProvider, private readonly IndexHandlerInterface $indexStructureHandler, - private readonly array $siblingProductsProviders = [], + private readonly SiblingProductsProvidersPool $siblingProductsProvidersPool, ) { - (fn (SiblingProductsProviderInterface ...$siblingProductsProviders) => $siblingProductsProviders)( - ...$this->siblingProductsProviders - ); } /** * Fill index with data. * * @param IndexName $indexName - * @param int $stockId - * @param array $skuList + * @param SkuListInStock $skuListInStock + * @param string $connectionName * @return void */ - public function fillIndex(IndexName $indexName, int $stockId, array $skuList = []): void + public function fillIndex(IndexName $indexName, SkuListInStock $skuListInStock, string $connectionName): void { - $select = $this->selectBuilder->getSelect($stockId, $skuList); - $data = $this->resourceConnection->getConnection()->fetchAll($select); - if ($skuList) { - $this->indexStructureHandler->cleanIndex( - $indexName, - new ArrayIterator($skuList), - ResourceConnection::DEFAULT_CONNECTION - ); - } - $this->indexStructureHandler->saveIndex( - $indexName, - new ArrayIterator($data), - ResourceConnection::DEFAULT_CONNECTION - ); + $stockId = $skuListInStock->getStockId(); + $this->reservationsIndexTable->createTable($stockId); + $this->prepareReservationsIndexData->execute($stockId); - foreach ($this->siblingProductsProviders as $siblingProductsProvider) { - if (!empty($skuList)) { - // partial reindex - $siblingSkus = $siblingProductsProvider->getSkus($skuList); - if (!$siblingSkus) { - continue; - } - } else { - // full reindex - $siblingSkus = []; + $skuList = $skuListInStock->getSkuList(); + if ($skuList) { + $productTypesBySkus = $this->getProductTypesBySkus->execute($skuList); + $productSkusByTypes = array_fill_keys(array_unique(array_values($productTypesBySkus)), []); + foreach ($productTypesBySkus as $sku => $type) { + $productSkusByTypes[$type][] = $sku; } - $data = $siblingProductsProvider->getData($indexName, $siblingSkus); - if ($siblingSkus) { + $sourceItemManagementTypes = $this->getSourceItemManagementProductTypes->execute(); + $composableSkusByTypes = array_intersect_key($productSkusByTypes, array_flip($sourceItemManagementTypes)); + $compositeSkusByTypes = array_diff_key($productSkusByTypes, $composableSkusByTypes); + + $composableSkus = array_merge([], ...array_values($composableSkusByTypes)); + if ($composableSkus) { + $data = $this->dataProvider->getData($stockId, $composableSkus); $this->indexStructureHandler->cleanIndex( $indexName, - new ArrayIterator($siblingSkus), - ResourceConnection::DEFAULT_CONNECTION + new ArrayIterator($composableSkus), + $connectionName ); + $this->indexStructureHandler->saveIndex($indexName, new ArrayIterator($data), $connectionName); + } + } else { + $data = $this->dataProvider->getData($stockId); + $this->indexStructureHandler->saveIndex($indexName, new ArrayIterator($data), $connectionName); + $composableSkus = array_keys($data); + $compositeSkusByTypes = []; + } + + $siblingsGroupedByType = $this->siblingProductsProvidersPool->getSiblingsGroupedByType($composableSkus); + foreach ($siblingsGroupedByType as $productType => $siblingSkus) { + $compositeSkusByTypes[$productType] = isset($compositeSkusByTypes[$productType]) + ? array_keys(array_flip($compositeSkusByTypes[$productType]) + array_flip($siblingSkus)) + : $siblingSkus; + } + + $indexAlias = IndexAlias::from($indexName->getAlias()->getValue()); + foreach ($compositeSkusByTypes as $productType => $skus) { + $data = $this->dataProvider->getSiblingsData($stockId, $productType, $skus, $indexAlias); + if ($skuList) { + // Partial reindex. When full reindex, replica table is used, so no cleaning is required. + $this->indexStructureHandler->cleanIndex($indexName, new ArrayIterator($skus), $connectionName); } - $this->indexStructureHandler->saveIndex( - $indexName, - new ArrayIterator($data), - ResourceConnection::DEFAULT_CONNECTION - ); + $this->indexStructureHandler->saveIndex($indexName, new ArrayIterator($data), $connectionName); } + + $this->reservationsIndexTable->dropTable($stockId); } } diff --git a/InventoryIndexer/Indexer/Stock/SkuListsProcessor.php b/InventoryIndexer/Indexer/Stock/SkuListsProcessor.php index fe11cf0e09f..cf99fc6eb40 100644 --- a/InventoryIndexer/Indexer/Stock/SkuListsProcessor.php +++ b/InventoryIndexer/Indexer/Stock/SkuListsProcessor.php @@ -12,20 +12,23 @@ use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryIndexer\Indexer\SourceItem\CompositeProductProcessorInterface as ProductProcessor; use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStock; -use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexStructureInterface; class SkuListsProcessor { + /** + * @var string + */ + private string $connectionName = ResourceConnection::DEFAULT_CONNECTION; + /** * @param GetSalableStatuses $getSalableStatuses * @param DefaultStockProviderInterface $defaultStockProvider * @param IndexNameBuilder $indexNameBuilder * @param IndexStructureInterface $indexStructure * @param IndexDataFiller $indexDataFiller - * @param ReservationsIndexTable $reservationsIndexTable - * @param PrepareReservationsIndexData $prepareReservationsIndexData * @param ProductProcessor[] $saleabilityChangesProcessorsPool */ public function __construct( @@ -34,8 +37,6 @@ public function __construct( private readonly IndexNameBuilder $indexNameBuilder, private readonly IndexStructureInterface $indexStructure, private readonly IndexDataFiller $indexDataFiller, - private readonly ReservationsIndexTable $reservationsIndexTable, - private readonly PrepareReservationsIndexData $prepareReservationsIndexData, private array $saleabilityChangesProcessorsPool = [], ) { // Sort processors by sort order @@ -64,19 +65,12 @@ public function reindexList(array $skuListInStockList): void $mainIndexName = $this->indexNameBuilder->setIndexId(InventoryIndexer::INDEXER_ID) ->addDimension('stock_', (string) $stockId) - ->setAlias(Alias::ALIAS_MAIN) + ->setAlias(IndexAlias::MAIN->value) ->build(); - if (!$this->indexStructure->isExist($mainIndexName, ResourceConnection::DEFAULT_CONNECTION)) { - $this->indexStructure->create($mainIndexName, ResourceConnection::DEFAULT_CONNECTION); + if (!$this->indexStructure->isExist($mainIndexName, $this->connectionName)) { + $this->indexStructure->create($mainIndexName, $this->connectionName); } - - $this->reservationsIndexTable->createTable($stockId); - $this->prepareReservationsIndexData->execute($stockId); - - $skuList = $skuListInStock->getSkuList(); - $this->indexDataFiller->fillIndex($mainIndexName, $stockId, $skuList); - - $this->reservationsIndexTable->dropTable($stockId); + $this->indexDataFiller->fillIndex($mainIndexName, $skuListInStock, $this->connectionName); } // Store products salable statuses after reindex diff --git a/InventoryIndexer/Indexer/Stock/Strategy/Sync.php b/InventoryIndexer/Indexer/Stock/Strategy/Sync.php index 11e6f62589f..ebbcaa9f118 100644 --- a/InventoryIndexer/Indexer/Stock/Strategy/Sync.php +++ b/InventoryIndexer/Indexer/Stock/Strategy/Sync.php @@ -9,11 +9,10 @@ use Magento\Framework\App\ResourceConnection; use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\InventoryIndexer; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; use Magento\InventoryIndexer\Indexer\Stock\GetAllStockIds; use Magento\InventoryIndexer\Indexer\Stock\IndexDataFiller; -use Magento\InventoryIndexer\Indexer\Stock\PrepareReservationsIndexData; -use Magento\InventoryIndexer\Indexer\Stock\ReservationsIndexTable; -use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexStructureInterface; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexTableSwitcherInterface; @@ -24,69 +23,28 @@ class Sync { /** - * @var GetAllStockIds + * @var string */ - private $getAllStockIds; + private string $connectionName = ResourceConnection::DEFAULT_CONNECTION; /** - * @var IndexStructureInterface - */ - private $indexStructure; - - /** - * @var IndexNameBuilder - */ - private $indexNameBuilder; - - /** - * @var IndexTableSwitcherInterface - */ - private $indexTableSwitcher; - - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - - /** - * @var ReservationsIndexTable - */ - private $reservationsIndexTable; - - /** - * @var PrepareReservationsIndexData - */ - private $prepareReservationsIndexData; - - /** - * $indexStructure is reserved name for construct variable in index internal mechanism - * * @param GetAllStockIds $getAllStockIds - * @param IndexStructureInterface $indexStructureHandler + * @param IndexStructureInterface $indexStructure * @param IndexNameBuilder $indexNameBuilder * @param IndexTableSwitcherInterface $indexTableSwitcher * @param DefaultStockProviderInterface $defaultStockProvider - * @param ReservationsIndexTable $reservationsIndexTable - * @param PrepareReservationsIndexData $prepareReservationsIndexData + * @param SkuListInStockFactory $skuListInStockFactory * @param IndexDataFiller $indexDataFiller */ public function __construct( - GetAllStockIds $getAllStockIds, - IndexStructureInterface $indexStructureHandler, - IndexNameBuilder $indexNameBuilder, - IndexTableSwitcherInterface $indexTableSwitcher, - DefaultStockProviderInterface $defaultStockProvider, - ReservationsIndexTable $reservationsIndexTable, - PrepareReservationsIndexData $prepareReservationsIndexData, + private readonly GetAllStockIds $getAllStockIds, + private readonly IndexStructureInterface $indexStructure, + private readonly IndexNameBuilder $indexNameBuilder, + private readonly IndexTableSwitcherInterface $indexTableSwitcher, + private readonly DefaultStockProviderInterface $defaultStockProvider, + private readonly SkuListInStockFactory $skuListInStockFactory, private readonly IndexDataFiller $indexDataFiller, ) { - $this->getAllStockIds = $getAllStockIds; - $this->indexStructure = $indexStructureHandler; - $this->indexNameBuilder = $indexNameBuilder; - $this->indexTableSwitcher = $indexTableSwitcher; - $this->defaultStockProvider = $defaultStockProvider; - $this->reservationsIndexTable = $reservationsIndexTable; - $this->prepareReservationsIndexData = $prepareReservationsIndexData; } /** @@ -124,34 +82,25 @@ public function executeList(array $stockIds): void continue; } - $replicaIndexName = $this->indexNameBuilder - ->setIndexId(InventoryIndexer::INDEXER_ID) - ->addDimension('stock_', (string)$stockId) - ->setAlias(Alias::ALIAS_REPLICA) + $replicaIndexName = $this->indexNameBuilder->setIndexId(InventoryIndexer::INDEXER_ID) + ->addDimension('stock_', (string) $stockId) + ->setAlias(IndexAlias::REPLICA->value) ->build(); + $this->indexStructure->delete($replicaIndexName, $this->connectionName); + $this->indexStructure->create($replicaIndexName, $this->connectionName); - $mainIndexName = $this->indexNameBuilder - ->setIndexId(InventoryIndexer::INDEXER_ID) - ->addDimension('stock_', (string)$stockId) - ->setAlias(Alias::ALIAS_MAIN) - ->build(); + $skuListInStock = $this->skuListInStockFactory->create(['stockId' => $stockId]); + $this->indexDataFiller->fillIndex($replicaIndexName, $skuListInStock, $this->connectionName); - $this->indexStructure->delete($replicaIndexName, ResourceConnection::DEFAULT_CONNECTION); - $this->indexStructure->create($replicaIndexName, ResourceConnection::DEFAULT_CONNECTION); - - if (!$this->indexStructure->isExist($mainIndexName, ResourceConnection::DEFAULT_CONNECTION)) { - $this->indexStructure->create($mainIndexName, ResourceConnection::DEFAULT_CONNECTION); + $mainIndexName = $this->indexNameBuilder->setIndexId(InventoryIndexer::INDEXER_ID) + ->addDimension('stock_', (string) $stockId) + ->setAlias(IndexAlias::MAIN->value) + ->build(); + if (!$this->indexStructure->isExist($mainIndexName, $this->connectionName)) { + $this->indexStructure->create($mainIndexName, $this->connectionName); } - - $this->reservationsIndexTable->createTable($stockId); - $this->prepareReservationsIndexData->execute($stockId); - - $this->indexDataFiller->fillIndex($replicaIndexName, $stockId); - - $this->indexTableSwitcher->switch($mainIndexName, ResourceConnection::DEFAULT_CONNECTION); - $this->indexStructure->delete($replicaIndexName, ResourceConnection::DEFAULT_CONNECTION); - - $this->reservationsIndexTable->dropTable($stockId); + $this->indexTableSwitcher->switch($mainIndexName, $this->connectionName); + $this->indexStructure->delete($replicaIndexName, $this->connectionName); } } } diff --git a/InventoryIndexer/Test/Unit/Indexer/CompositeProductsIndexerTest.php b/InventoryIndexer/Test/Unit/Indexer/CompositeProductsIndexerTest.php new file mode 100644 index 00000000000..0e26200cf2e --- /dev/null +++ b/InventoryIndexer/Test/Unit/Indexer/CompositeProductsIndexerTest.php @@ -0,0 +1,133 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryIndexer\Test\Unit\Indexer; + +use Magento\InventoryApi\Model\GetStockIdsBySkusInterface; +use Magento\InventoryCatalogApi\Model\CompositeProductTypesProviderInterface; +use Magento\InventoryCatalogApi\Model\GetChildrenSkusOfParentSkusInterface; +use Magento\InventoryCatalogApi\Model\GetProductTypesBySkusInterface; +use Magento\InventoryIndexer\Indexer\CompositeProductsIndexer; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStock; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStockFactory; +use Magento\InventoryIndexer\Indexer\Stock\SkuListsProcessor; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CompositeProductsIndexerTest extends TestCase +{ + /** + * @var GetProductTypesBySkusInterface|MockObject + */ + private $getProductTypesBySkusMock; + + /** + * @var GetChildrenSkusOfParentSkusInterface|MockObject + */ + private $getChildrenSkusOfParentSkusMock; + + /** + * @var GetStockIdsBySkusInterface|MockObject + */ + private $getStockIdsBySkusMock; + + /** + * @var SkuListInStockFactory|MockObject + */ + private $skuListInStockFactoryMock; + + /** + * @var SkuListsProcessor|MockObject + */ + private $skuListsProcessorMock; + + /** + * @var CompositeProductsIndexer + */ + private $indexer; + + protected function setUp(): void + { + $compositeProductTypesProviderMock = $this->createMock(CompositeProductTypesProviderInterface::class); + $this->getProductTypesBySkusMock = $this->createMock(GetProductTypesBySkusInterface::class); + $this->getChildrenSkusOfParentSkusMock = $this->createMock(GetChildrenSkusOfParentSkusInterface::class); + $this->getStockIdsBySkusMock = $this->createMock(GetStockIdsBySkusInterface::class); + $this->skuListInStockFactoryMock = $this->createMock(SkuListInStockFactory::class); + $this->skuListsProcessorMock = $this->createMock(SkuListsProcessor::class); + + $compositeProductTypesProviderMock->method('execute') + ->willReturn(['bundle', 'configurable', 'grouped']); + + $this->indexer = new CompositeProductsIndexer( + $compositeProductTypesProviderMock, + $this->getProductTypesBySkusMock, + $this->getChildrenSkusOfParentSkusMock, + $this->getStockIdsBySkusMock, + $this->skuListInStockFactoryMock, + $this->skuListsProcessorMock, + ); + } + + public function testReindexListEmptyList(): void + { + $this->getProductTypesBySkusMock->expects(self::never())->method('execute'); + $this->skuListsProcessorMock->expects(self::never())->method('reindexList'); + $this->indexer->reindexList([]); + } + + public function testReindexListNoCompositePresent(): void + { + $skus = ['simple-1', 'simple-2']; + + $this->getProductTypesBySkusMock->expects(self::once())->method('execute')->with($skus) + ->willReturn([ + 'simple-1' => 'simple', + 'simple-2' => 'simple', + ]); + $this->getChildrenSkusOfParentSkusMock->expects(self::never())->method('execute'); + $this->skuListsProcessorMock->expects(self::never())->method('reindexList'); + + $this->indexer->reindexList($skus); + } + + public function testReindexList(): void + { + $skus = ['bundle-1', 'simple-2', 'configurable-3', 'grouped-4']; + + $this->getProductTypesBySkusMock->expects(self::once())->method('execute')->with($skus) + ->willReturn([ + 'bundle-1' => 'bundle', + 'simple-2' => 'simple', + 'configurable-3' => 'configurable', + 'grouped-4' => 'grouped', + ]); + $this->getChildrenSkusOfParentSkusMock->expects(self::once())->method('execute') + ->with(['bundle-1', 'configurable-3', 'grouped-4']) + ->willReturn([ + 'bundle1' => [], + 'configurable-3' => ['configurable1-red', 'configurable1-green'], + 'grouped-4' => ['simple1'], + ]); + $this->getStockIdsBySkusMock->expects(self::exactly(2))->method('execute') + ->willReturnMap([ + [['configurable1-red', 'configurable1-green'], [2]], + [['simple1'], [2,3]], + ]); + + $stock2Mock = $this->createMock(SkuListInStock::class); + $stock3Mock = $this->createMock(SkuListInStock::class); + $this->skuListInStockFactoryMock->expects(self::exactly(2))->method('create') + ->willReturnMap([ + [['stockId' => 2, 'skuList' => ['configurable-3', 'grouped-4']], $stock2Mock], + [['stockId' => 3, 'skuList' => ['grouped-4']], $stock3Mock], + ]); + $this->skuListsProcessorMock->expects(self::once())->method('reindexList') + ->with([$stock2Mock, $stock3Mock]); + + $this->indexer->reindexList($skus); + } +} diff --git a/InventoryIndexer/Test/Unit/Indexer/SiblingProductsProvidersPoolTest.php b/InventoryIndexer/Test/Unit/Indexer/SiblingProductsProvidersPoolTest.php new file mode 100644 index 00000000000..ab0c4b53308 --- /dev/null +++ b/InventoryIndexer/Test/Unit/Indexer/SiblingProductsProvidersPoolTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryIndexer\Test\Unit\Indexer; + +use Magento\InventoryIndexer\Indexer\SiblingProductsProviderInterface; +use Magento\InventoryIndexer\Indexer\SiblingProductsProvidersPool; +use PHPUnit\Framework\TestCase; + +class SiblingProductsProvidersPoolTest extends TestCase +{ + public function testGetSiblingsGroupedByTypeEmptyPool(): void + { + $model = new SiblingProductsProvidersPool([]); + self::assertCount(0, $model->getSiblingsGroupedByType([])); + } + + public function testGetSiblingsGroupedByTypeNoSiblings(): void + { + $skus = ['simple-1', 'simple-2']; + + $bundleProductsProvider = $this->createMock(SiblingProductsProviderInterface::class); + $bundleProductsProvider->expects(self::once())->method('getSkus')->willReturn($skus)->willReturn([]); + + $model = new SiblingProductsProvidersPool(['bundle' => $bundleProductsProvider]); + self::assertCount(0, $model->getSiblingsGroupedByType($skus)); + } + + public function testGetSiblingsGroupedByType(): void + { + $skus = ['simple-1', 'simple-2']; + + $bundleProductsProvider = $this->createMock(SiblingProductsProviderInterface::class); + $bundleProductsProvider->expects(self::once())->method('getSkus')->willReturn($skus) + ->willReturn(['bundle-1', 'bundle-2']); + $groupedProductsProvider = $this->createMock(SiblingProductsProviderInterface::class); + $groupedProductsProvider->expects(self::once())->method('getSkus')->willReturn($skus) + ->willReturn(['grouped-1']); + + $model = new SiblingProductsProvidersPool( + ['bundle' => $bundleProductsProvider, 'grouped' => $groupedProductsProvider] + ); + $result = $model->getSiblingsGroupedByType($skus); + self::assertEquals(['bundle' => ['bundle-1', 'bundle-2'], 'grouped' => ['grouped-1']], $result); + } +} diff --git a/InventoryIndexer/Test/Unit/Indexer/SourceItem/SkuListInStockTest.php b/InventoryIndexer/Test/Unit/Indexer/SourceItem/SkuListInStockTest.php new file mode 100644 index 00000000000..1f7138b6248 --- /dev/null +++ b/InventoryIndexer/Test/Unit/Indexer/SourceItem/SkuListInStockTest.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryIndexer\Test\Unit\Indexer\SourceItem; + +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStock; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[ + CoversClass(SkuListInStock::class), +] +class SkuListInStockTest extends TestCase +{ + public function testCreate(): void + { + $stockId = 2; + $skuList = ['sku1', 'sku2']; + $skuListInStock = new SkuListInStock($stockId, $skuList); + self::assertEquals($stockId, $skuListInStock->getStockId()); + self::assertEquals($skuList, $skuListInStock->getSkuList()); + } +} diff --git a/InventoryIndexer/Test/Unit/Indexer/Stock/DataProviderTest.php b/InventoryIndexer/Test/Unit/Indexer/Stock/DataProviderTest.php new file mode 100644 index 00000000000..3942281c9a0 --- /dev/null +++ b/InventoryIndexer/Test/Unit/Indexer/Stock/DataProviderTest.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryIndexer\Test\Unit\Indexer\Stock; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; +use Magento\InventoryIndexer\Indexer\SelectBuilder; +use Magento\InventoryIndexer\Indexer\SiblingSelectBuilderInterface; +use Magento\InventoryIndexer\Indexer\Stock\DataProvider; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class DataProviderTest extends TestCase +{ + /** + * @var SelectBuilder|MockObject + */ + private $selectBuilderMock; + + /** + * @var SiblingSelectBuilderInterface|MockObject + */ + private $bundleSelectBuilderMock; + + /** + * @var SiblingSelectBuilderInterface|MockObject + */ + private $groupedSelectBuilderMock; + + /** + * @var AdapterInterface|MockObject + */ + private $connectionMock; + + /** + * @var DataProvider + */ + private $dataProvider; + + protected function setUp(): void + { + $resourceConnectionMock = $this->createMock(ResourceConnection::class); + $this->selectBuilderMock = $this->createMock(SelectBuilder::class); + $this->bundleSelectBuilderMock = $this->createMock(SiblingSelectBuilderInterface::class); + $this->groupedSelectBuilderMock = $this->createMock(SiblingSelectBuilderInterface::class); + + $this->connectionMock = $this->createMock(AdapterInterface::class); + $resourceConnectionMock->method('getConnection')->willReturn($this->connectionMock); + + $this->dataProvider = new DataProvider( + $resourceConnectionMock, + $this->selectBuilderMock, + ['bundle' => $this->bundleSelectBuilderMock, 'grouped' => $this->groupedSelectBuilderMock], + ); + } + + public function testGetData(): void + { + $stockId = 2; + $skuList = ['sku1', 'sku2']; + $data = [ + ['sku' => 'sku1', 'quantity' => 10, 'is_salable' => 1], + ['sku' => 'sku2', 'quantity' => 0, 'is_salable' => 0], + ]; + + $selectMock = $this->createMock(Select::class); + $this->selectBuilderMock->expects(self::once())->method('getSelect')->with($stockId)->willReturn($selectMock); + $this->connectionMock->expects(self::once())->method('fetchAll')->with($selectMock)->willReturn($data); + $this->bundleSelectBuilderMock->expects(self::never())->method('getSelect'); + $this->groupedSelectBuilderMock->expects(self::never())->method('getSelect'); + + $result = $this->dataProvider->getData($stockId, $skuList); + self::assertEquals(array_combine($skuList, $data), $result); + } + + public function testGetSiblingsData(): void + { + $stockId = 2; + $skuList = ['bundle1', 'bundle2']; + $data = [ + ['sku' => 'bundle1', 'quantity' => 10, 'is_salable' => 1], + ['sku' => 'bundle2', 'quantity' => 0, 'is_salable' => 0], + ]; + + $selectMock = $this->createMock(Select::class); + $this->bundleSelectBuilderMock->expects(self::once()) + ->method('getSelect') + ->with($stockId, $skuList) + ->willReturn($selectMock); + $this->connectionMock->expects(self::once())->method('fetchAll')->with($selectMock)->willReturn($data); + $this->selectBuilderMock->expects(self::never())->method('getSelect'); + $this->groupedSelectBuilderMock->expects(self::never())->method('getSelect'); + + $result = $this->dataProvider->getSiblingsData($stockId, 'bundle', $skuList); + self::assertEquals(array_combine($skuList, $data), $result); + } +} diff --git a/InventoryIndexer/Test/Unit/Indexer/Stock/IndexDataFillerTest.php b/InventoryIndexer/Test/Unit/Indexer/Stock/IndexDataFillerTest.php new file mode 100644 index 00000000000..4ebb22aaf18 --- /dev/null +++ b/InventoryIndexer/Test/Unit/Indexer/Stock/IndexDataFillerTest.php @@ -0,0 +1,369 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryIndexer\Test\Unit\Indexer\Stock; + +use Magento\InventoryCatalogApi\Model\GetProductTypesBySkusInterface; +use Magento\InventoryConfigurationApi\Model\GetAllowedProductTypesForSourceItemManagementInterface; +use Magento\InventoryIndexer\Indexer\SiblingProductsProvidersPool; +use Magento\InventoryIndexer\Indexer\SourceItem\SkuListInStock; +use Magento\InventoryIndexer\Indexer\Stock\DataProvider; +use Magento\InventoryIndexer\Indexer\Stock\IndexDataFiller; +use Magento\InventoryIndexer\Indexer\Stock\PrepareReservationsIndexData; +use Magento\InventoryIndexer\Indexer\Stock\ReservationsIndexTable; +use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias as IndexAliasModel; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexAlias; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexHandlerInterface; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexName; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +#[ + CoversClass(IndexDataFiller::class), +] +class IndexDataFillerTest extends TestCase +{ + /** + * @var ReservationsIndexTable|MockObject + */ + private $reservationsIndexTableMock; + + /** + * @var PrepareReservationsIndexData|MockObject + */ + private $prepareReservationsIndexDataMock; + + /** + * @var GetProductTypesBySkusInterface|MockObject + */ + private $getProductTypesBySkusMock; + + /** + * @var DataProvider|MockObject + */ + private $dataProviderMock; + + /** + * @var IndexHandlerInterface|MockObject + */ + private $indexStructureHandlerMock; + + /** + * @var SiblingProductsProvidersPool|MockObject + */ + private $siblingProductsProvidersPoolMock; + + /** + * @var string + */ + private $connectionName; + + /** + * @var IndexDataFiller + */ + private $indexDataFiller; + + protected function setUp(): void + { + $this->reservationsIndexTableMock = $this->createMock(ReservationsIndexTable::class); + $this->prepareReservationsIndexDataMock = $this->createMock(PrepareReservationsIndexData::class); + $this->getProductTypesBySkusMock = $this->createMock(GetProductTypesBySkusInterface::class); + $getSourceItemManagementProductTypesMock = $this->createMock( + GetAllowedProductTypesForSourceItemManagementInterface::class + ); + $this->dataProviderMock = $this->createMock(DataProvider::class); + $this->indexStructureHandlerMock = $this->createMock(IndexHandlerInterface::class); + $this->siblingProductsProvidersPoolMock = $this->createMock(SiblingProductsProvidersPool::class); + + $getSourceItemManagementProductTypesMock->method('execute')->willReturn(['simple']); + $this->connectionName = 'default'; + + $this->indexDataFiller = new IndexDataFiller( + $this->reservationsIndexTableMock, + $this->prepareReservationsIndexDataMock, + $this->getProductTypesBySkusMock, + $getSourceItemManagementProductTypesMock, + $this->dataProviderMock, + $this->indexStructureHandlerMock, + $this->siblingProductsProvidersPoolMock, + ); + } + + public function testFillIndexSimpleProducts(): void + { + $stockId = 2; + $simpleSkuList = ['simple-1', 'simple-2']; + + $aliasMock = $this->createMock(IndexAliasModel::class); + $aliasMock->method('getValue')->willReturn(IndexAlias::MAIN->value); + $indexNameMock = $this->createMock(IndexName::class); + $indexNameMock->method('getAlias')->willReturn($aliasMock); + $skuListInStockMock = $this->createMock(SkuListInStock::class); + $skuListInStockMock->method('getStockId')->willReturn($stockId); + $skuListInStockMock->method('getSkuList')->willReturn($simpleSkuList); + + $this->reservationsIndexTableMock->expects(self::once())->method('createTable')->with($stockId); + $this->prepareReservationsIndexDataMock->expects(self::once())->method('execute')->with($stockId); + $this->reservationsIndexTableMock->expects(self::once())->method('dropTable')->with($stockId); + + $typesBySkus = [ + 'simple-1' => 'simple', + 'simple-2' => 'simple', + ]; + $this->getProductTypesBySkusMock->expects(self::once()) + ->method('execute') + ->with($simpleSkuList) + ->willReturn($typesBySkus); + $data = [ + 'simple-1' => ['sku' => 'simple-1', 'quantity' => 10, 'is_salable' => true], + 'simple-2' => ['sku' => 'simple-2', 'quantity' => 0, 'is_salable' => false], + ]; + $this->dataProviderMock->expects(self::once()) + ->method('getData') + ->with($stockId, $simpleSkuList) + ->willReturn($data); + $this->indexStructureHandlerMock->expects(self::once()) + ->method('cleanIndex') + ->with($indexNameMock, new \ArrayIterator($simpleSkuList), $this->connectionName); + $this->indexStructureHandlerMock->expects(self::once()) + ->method('saveIndex') + ->with($indexNameMock, new \ArrayIterator($data), $this->connectionName); + $this->siblingProductsProvidersPoolMock->expects(self::once()) + ->method('getSiblingsGroupedByType') + ->with($simpleSkuList) + ->willReturn([]); + $this->dataProviderMock->expects(self::never())->method('getSiblingsData'); + + $this->indexDataFiller->fillIndex($indexNameMock, $skuListInStockMock, $this->connectionName); + } + + public function testFillIndexSimpleProductsWithParents(): void + { + $stockId = 2; + $simpleSkuList = ['simple-1', 'simple-2']; + + $aliasMock = $this->createMock(IndexAliasModel::class); + $aliasMock->method('getValue')->willReturn(IndexAlias::MAIN->value); + $indexNameMock = $this->createMock(IndexName::class); + $indexNameMock->method('getAlias')->willReturn($aliasMock); + $skuListInStockMock = $this->createMock(SkuListInStock::class); + $skuListInStockMock->method('getStockId')->willReturn($stockId); + $skuListInStockMock->method('getSkuList')->willReturn($simpleSkuList); + + $this->reservationsIndexTableMock->expects(self::once())->method('createTable')->with($stockId); + $this->prepareReservationsIndexDataMock->expects(self::once())->method('execute')->with($stockId); + $this->reservationsIndexTableMock->expects(self::once())->method('dropTable')->with($stockId); + + $typesBySkus = [ + 'simple-1' => 'simple', + 'simple-2' => 'simple', + ]; + $this->getProductTypesBySkusMock->expects(self::once()) + ->method('execute') + ->with($simpleSkuList) + ->willReturn($typesBySkus); + $simpleData = [ + 'simple-1' => ['sku' => 'simple-1', 'quantity' => 10, 'is_salable' => true], + 'simple-2' => ['sku' => 'simple-2', 'quantity' => 0, 'is_salable' => false], + ]; + $this->dataProviderMock->expects(self::once()) + ->method('getData') + ->with($stockId, $simpleSkuList) + ->willReturn($simpleData); + $compositeSkuList = ['grouped-1', 'grouped-2']; + $this->siblingProductsProvidersPoolMock->expects(self::once()) + ->method('getSiblingsGroupedByType') + ->with($simpleSkuList) + ->willReturn(['grouped' => $compositeSkuList]); + $compositeData = [ + 'grouped-1' => ['sku' => 'grouped-1', 'quantity' => 15, 'is_salable' => true], + 'grouped-2' => ['sku' => 'grouped-2', 'quantity' => 0, 'is_salable' => false], + ]; + $this->dataProviderMock->expects(self::once()) + ->method('getSiblingsData') + ->with($stockId, 'grouped', $compositeSkuList, IndexAlias::MAIN) + ->willReturn($compositeData); + + $this->indexStructureHandlerMock->expects(self::exactly(2)) + ->method('cleanIndex') + ->willReturnCallback( + function (...$args) use ($indexNameMock, $simpleSkuList, $compositeSkuList) { + static $callIndex = 0; + $callIndex++; + self::assertEquals($indexNameMock, $args[0]); + self::assertEquals($this->connectionName, $args[2]); + if ($callIndex === 1) { + self::assertEquals(new \ArrayIterator($simpleSkuList), $args[1]); + } elseif ($callIndex === 2) { + self::assertEquals(new \ArrayIterator($compositeSkuList), $args[1]); + } + } + ); + $this->indexStructureHandlerMock->expects(self::exactly(2)) + ->method('saveIndex') + ->willReturnCallback( + function (...$args) use ($indexNameMock, $simpleData, $compositeData) { + static $callIndex = 0; + $callIndex++; + self::assertEquals($indexNameMock, $args[0]); + self::assertEquals($this->connectionName, $args[2]); + if ($callIndex === 1) { + self::assertEquals(new \ArrayIterator($simpleData), $args[1]); + } elseif ($callIndex === 2) { + self::assertEquals(new \ArrayIterator($compositeData), $args[1]); + } + } + ); + + $this->indexDataFiller->fillIndex($indexNameMock, $skuListInStockMock, $this->connectionName); + } + + public function testFillIndexCompositeProducts(): void + { + $stockId = 2; + $compositeSkuList = ['bundle-1', 'bundle-2']; + + $aliasMock = $this->createMock(IndexAliasModel::class); + $aliasMock->method('getValue')->willReturn(IndexAlias::MAIN->value); + $indexNameMock = $this->createMock(IndexName::class); + $indexNameMock->method('getAlias')->willReturn($aliasMock); + $skuListInStockMock = $this->createMock(SkuListInStock::class); + $skuListInStockMock->method('getStockId')->willReturn($stockId); + $skuListInStockMock->method('getSkuList')->willReturn($compositeSkuList); + + $this->reservationsIndexTableMock->expects(self::once())->method('createTable')->with($stockId); + $this->prepareReservationsIndexDataMock->expects(self::once())->method('execute')->with($stockId); + $this->reservationsIndexTableMock->expects(self::once())->method('dropTable')->with($stockId); + + $typesBySkus = [ + 'bundle-1' => 'bundle', + 'bundle-2' => 'bundle', + ]; + $this->getProductTypesBySkusMock->expects(self::once()) + ->method('execute') + ->with($compositeSkuList) + ->willReturn($typesBySkus); + $this->dataProviderMock->expects(self::never())->method('getData'); + $this->siblingProductsProvidersPoolMock->expects(self::once()) + ->method('getSiblingsGroupedByType') + ->with([]) + ->willReturn([]); + $data = [ + 'bundle-1' => ['sku' => 'bundle-1', 'quantity' => 0, 'is_salable' => false], + 'bundle-2' => ['sku' => 'bundle-2', 'quantity' => 20, 'is_salable' => true], + ]; + $this->dataProviderMock->expects(self::once()) + ->method('getSiblingsData') + ->with($stockId, 'bundle', $compositeSkuList, IndexAlias::MAIN) + ->willReturn($data); + $this->indexStructureHandlerMock->expects(self::once()) + ->method('cleanIndex') + ->with($indexNameMock, new \ArrayIterator($compositeSkuList), $this->connectionName); + $this->indexStructureHandlerMock->expects(self::once()) + ->method('saveIndex') + ->with($indexNameMock, new \ArrayIterator($data), $this->connectionName); + + $this->indexDataFiller->fillIndex($indexNameMock, $skuListInStockMock, $this->connectionName); + } + + public function testFillIndexMixedProducts(): void + { + $stockId = 2; + $skuList = ['bundle-1', 'simple-1', 'simple-2', 'grouped-1']; + + $aliasMock = $this->createMock(IndexAliasModel::class); + $aliasMock->method('getValue')->willReturn(IndexAlias::MAIN->value); + $indexNameMock = $this->createMock(IndexName::class); + $indexNameMock->method('getAlias')->willReturn($aliasMock); + $skuListInStockMock = $this->createMock(SkuListInStock::class); + $skuListInStockMock->method('getStockId')->willReturn($stockId); + $skuListInStockMock->method('getSkuList')->willReturn($skuList); + + $this->reservationsIndexTableMock->expects(self::once())->method('createTable')->with($stockId); + $this->prepareReservationsIndexDataMock->expects(self::once())->method('execute')->with($stockId); + $this->reservationsIndexTableMock->expects(self::once())->method('dropTable')->with($stockId); + + $typesBySkus = [ + 'bundle-1' => 'bundle', + 'simple-1' => 'simple', + 'simple-2' => 'simple', + 'grouped-1' => 'grouped', + ]; + $this->getProductTypesBySkusMock->expects(self::once()) + ->method('execute') + ->with($skuList) + ->willReturn($typesBySkus); + $simpleData = [ + 'simple-1' => ['sku' => 'simple-1', 'quantity' => 10, 'is_salable' => true], + 'simple-2' => ['sku' => 'simple-2', 'quantity' => 0, 'is_salable' => false], + ]; + $this->dataProviderMock->expects(self::once()) + ->method('getData') + ->with($stockId, ['simple-1', 'simple-2']) + ->willReturn($simpleData); + $this->siblingProductsProvidersPoolMock->expects(self::once()) + ->method('getSiblingsGroupedByType') + ->with(['simple-1', 'simple-2']) + ->willReturn(['bundle' => ['bundle-2', 'bundle-3'], 'grouped' => ['grouped-1', 'grouped-2']]); + $bundleData = [ + 'bundle-1' => ['sku' => 'bundle-1', 'quantity' => 15, 'is_salable' => true], + 'bundle-2' => ['sku' => 'bundle-2', 'quantity' => 25, 'is_salable' => true], + 'bundle-3' => ['sku' => 'bundle-3', 'quantity' => 0, 'is_salable' => false], + ]; + $groupedData = [ + 'grouped-1' => ['sku' => 'grouped-1', 'quantity' => 0, 'is_salable' => false], + 'grouped-2' => ['sku' => 'grouped-2', 'quantity' => 30, 'is_salable' => true], + ]; + $this->dataProviderMock->expects(self::exactly(2)) + ->method('getSiblingsData') + ->willReturnMap([ + [$stockId, 'bundle', ['bundle-1', 'bundle-2', 'bundle-3'], IndexAlias::MAIN, $bundleData], + [$stockId, 'grouped', ['grouped-1', 'grouped-2'], IndexAlias::MAIN, $groupedData], + ]); + + $this->indexStructureHandlerMock->expects(self::exactly(3)) + ->method('cleanIndex') + ->willReturnCallback( + function (...$args) use ($indexNameMock) { + static $callIndex = 0; + $callIndex++; + self::assertEquals($indexNameMock, $args[0]); + self::assertEquals($this->connectionName, $args[2]); + if ($callIndex === 1) { + self::assertEquals(new \ArrayIterator(['simple-1', 'simple-2']), $args[1]); + } elseif ($callIndex === 2) { + self::assertEquals(new \ArrayIterator(['bundle-1', 'bundle-2', 'bundle-3']), $args[1]); + } elseif ($callIndex === 3) { + self::assertEquals(new \ArrayIterator(['grouped-1', 'grouped-2']), $args[1]); + } + } + ); + $this->indexStructureHandlerMock->expects(self::exactly(3)) + ->method('saveIndex') + ->willReturnCallback( + function (...$args) use ($indexNameMock, $simpleData, $bundleData, $groupedData) { + static $callIndex = 0; + $callIndex++; + self::assertEquals($indexNameMock, $args[0]); + self::assertEquals($this->connectionName, $args[2]); + if ($callIndex === 1) { + self::assertEquals(new \ArrayIterator($simpleData), $args[1]); + } elseif ($callIndex === 2) { + self::assertEquals(new \ArrayIterator($bundleData), $args[1]); + } elseif ($callIndex === 3) { + self::assertEquals(new \ArrayIterator($groupedData), $args[1]); + } + } + ); + + $this->indexDataFiller->fillIndex($indexNameMock, $skuListInStockMock, $this->connectionName); + } +} diff --git a/InventoryMultiDimensionalIndexerApi/Model/Alias.php b/InventoryMultiDimensionalIndexerApi/Model/Alias.php index 2cca5dc1cf9..ecd1d357d66 100644 --- a/InventoryMultiDimensionalIndexerApi/Model/Alias.php +++ b/InventoryMultiDimensionalIndexerApi/Model/Alias.php @@ -19,20 +19,24 @@ class Alias { /** * Replica index alias + * + * @deprecated + * @see IndexAlias::REPLICA */ - public const ALIAS_REPLICA = 'replica'; + public const ALIAS_REPLICA = IndexAlias::REPLICA->value; /** * Main index alias + * + * @deprecated + * @see IndexAlias::MAIN */ - public const ALIAS_MAIN = 'main'; + public const ALIAS_MAIN = IndexAlias::MAIN->value; /** - * One of self::ALIAS_* - * - * @var string + * @var IndexAlias */ - private $value; + private IndexAlias $value; /** * @param string $value One of self::ALIAS_* @@ -40,10 +44,11 @@ class Alias */ public function __construct(string $value) { - if ($value !== self::ALIAS_REPLICA && $value !== self::ALIAS_MAIN) { + try { + $this->value = IndexAlias::from($value); + } catch (\ValueError) { throw new LocalizedException(new Phrase('Wrong value %value for alias', ['value' => $value])); } - $this->value = $value; } /** @@ -53,6 +58,6 @@ public function __construct(string $value) */ public function getValue(): string { - return $this->value; + return $this->value->value; } } diff --git a/InventoryMultiDimensionalIndexerApi/Model/IndexAlias.php b/InventoryMultiDimensionalIndexerApi/Model/IndexAlias.php new file mode 100644 index 00000000000..a07ff1588dd --- /dev/null +++ b/InventoryMultiDimensionalIndexerApi/Model/IndexAlias.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\InventoryMultiDimensionalIndexerApi\Model; + +/** + * Aliases for inventory indexer. + */ +enum IndexAlias: string +{ + case MAIN = 'main'; + case REPLICA = 'replica'; +} diff --git a/InventorySourceSelectionApi/Model/Algorithms/Result/GetDefaultSortedSourcesResult.php b/InventorySourceSelectionApi/Model/Algorithms/Result/GetDefaultSortedSourcesResult.php index f723443e82a..018ab245e39 100644 --- a/InventorySourceSelectionApi/Model/Algorithms/Result/GetDefaultSortedSourcesResult.php +++ b/InventorySourceSelectionApi/Model/Algorithms/Result/GetDefaultSortedSourcesResult.php @@ -96,7 +96,7 @@ public function execute( $itemsTdDeliver = []; foreach ($inventoryRequest->getItems() as $item) { $normalizedSku = $this->normalizeSku(trim($item->getSku())); - $itemsTdDeliver[$normalizedSku] = $item->getQty(); + $itemsTdDeliver[$normalizedSku] = ($itemsTdDeliver[$normalizedSku] ?? 0) + $item->getQty(); } $sortedSourceCodes = []; diff --git a/InventorySourceSelectionApi/Test/Integration/GetDefaultSortedSourcesResultTest.php b/InventorySourceSelectionApi/Test/Integration/GetDefaultSortedSourcesResultTest.php index d837b2ecc82..4c47fbd6111 100644 --- a/InventorySourceSelectionApi/Test/Integration/GetDefaultSortedSourcesResultTest.php +++ b/InventorySourceSelectionApi/Test/Integration/GetDefaultSortedSourcesResultTest.php @@ -132,6 +132,19 @@ public static function shouldReturnDefaultResultsDataProvider(): array ], true ], + [ + 20, + [ + ['sku' => 'SKU-2', 'qty' => 1], ['sku' => 'SKU-2', 'qty' => 2], + ], + [ + 'us-1', + ], + [ + 'us-1/SKU-2' => ['deduct' => 3, 'avail' => 5], + ], + true + ], ]; }