Skip to content

Commit beb32b1

Browse files
committed
MAGETWO-90729: Imported products are incorrectly in stock if 'allow_backorders' enabled
1 parent 92c3ac6 commit beb32b1

File tree

6 files changed

+216
-33
lines changed

6 files changed

+216
-33
lines changed

app/code/Magento/CatalogImportExport/Model/Import/Product.php

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,40 +2151,8 @@ protected function _saveStockItem()
21512151
$row = [];
21522152
$sku = $rowData[self::COL_SKU];
21532153
if ($this->skuProcessor->getNewSku($sku) !== null) {
2154-
$row['product_id'] = $this->skuProcessor->getNewSku($sku)['entity_id'];
2154+
$row = $this->formatStockDataForRow($rowData);
21552155
$productIdsToReindex[] = $row['product_id'];
2156-
2157-
$row['website_id'] = $this->stockConfiguration->getDefaultScopeId();
2158-
$row['stock_id'] = $this->stockRegistry->getStock($row['website_id'])->getStockId();
2159-
2160-
$stockItemDo = $this->stockRegistry->getStockItem($row['product_id'], $row['website_id']);
2161-
$existStockData = $stockItemDo->getData();
2162-
2163-
$row = array_merge(
2164-
$this->defaultStockData,
2165-
array_intersect_key($existStockData, $this->defaultStockData),
2166-
array_intersect_key($rowData, $this->defaultStockData),
2167-
$row
2168-
);
2169-
$row['sku'] = $sku;
2170-
2171-
if ($this->stockConfiguration->isQty(
2172-
$this->skuProcessor->getNewSku($sku)['type_id']
2173-
)
2174-
) {
2175-
$stockItemDo->setData($row);
2176-
$row['is_in_stock'] = $this->stockStateProvider->verifyStock($stockItemDo);
2177-
if ($this->stockStateProvider->verifyNotification($stockItemDo)) {
2178-
$row['low_stock_date'] = gmdate(
2179-
'Y-m-d H:i:s',
2180-
(new \DateTime())->getTimestamp()
2181-
);
2182-
}
2183-
$row['stock_status_changed_auto'] =
2184-
(int)!$this->stockStateProvider->verifyStock($stockItemDo);
2185-
} else {
2186-
$row['qty'] = 0;
2187-
}
21882156
}
21892157

21902158
if (!isset($stockData[$sku])) {
@@ -2875,4 +2843,46 @@ private function getExistingSku($sku)
28752843
{
28762844
return $this->_oldSku[strtolower($sku)];
28772845
}
2846+
2847+
/**
2848+
* Format row data to DB compatible values.
2849+
*
2850+
* @param array $rowData
2851+
* @return array
2852+
*/
2853+
private function formatStockDataForRow(array $rowData): array
2854+
{
2855+
$sku = $rowData[self::COL_SKU];
2856+
$row['product_id'] = $this->skuProcessor->getNewSku($sku)['entity_id'];
2857+
$row['website_id'] = $this->stockConfiguration->getDefaultScopeId();
2858+
$row['stock_id'] = $this->stockRegistry->getStock($row['website_id'])->getStockId();
2859+
2860+
$stockItemDo = $this->stockRegistry->getStockItem($row['product_id'], $row['website_id']);
2861+
$existStockData = $stockItemDo->getData();
2862+
2863+
$row = array_merge(
2864+
$this->defaultStockData,
2865+
array_intersect_key($existStockData, $this->defaultStockData),
2866+
array_intersect_key($rowData, $this->defaultStockData),
2867+
$row
2868+
);
2869+
2870+
if ($this->stockConfiguration->isQty($this->skuProcessor->getNewSku($sku)['type_id'])) {
2871+
$stockItemDo->setData($row);
2872+
$row['is_in_stock'] = $stockItemDo->getBackorders() && isset($row['is_in_stock'])
2873+
? $row['is_in_stock']
2874+
: $this->stockStateProvider->verifyStock($stockItemDo);
2875+
if ($this->stockStateProvider->verifyNotification($stockItemDo)) {
2876+
$row['low_stock_date'] = $this->dateTime->gmDate(
2877+
'Y-m-d H:i:s',
2878+
(new \DateTime())->getTimestamp()
2879+
);
2880+
}
2881+
$row['stock_status_changed_auto'] = (int)!$this->stockStateProvider->verifyStock($stockItemDo);
2882+
} else {
2883+
$row['qty'] = 0;
2884+
}
2885+
2886+
return $row;
2887+
}
28782888
}

dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* See COPYING.txt for license details.
55
*/
66

7+
declare(strict_types=1);
8+
79
namespace Magento\Catalog\Model;
810

911
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -552,4 +554,47 @@ public function testGetOptions()
552554
}
553555
}
554556
}
557+
558+
/**
559+
* Check stock status changing if backorders functionality enabled.
560+
*
561+
* @magentoDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php
562+
* @dataProvider productWithBackordersDataProvider
563+
* @param int $qty
564+
* @param int $stockStatus
565+
* @param bool $expectedStockStatus
566+
*
567+
* @return void
568+
*/
569+
public function testSaveWithBackordersEnabled(int $qty, int $stockStatus, bool $expectedStockStatus): void
570+
{
571+
$product = $this->productRepository->get('simple-out-of-stock', true, null, true);
572+
$stockItem = $product->getExtensionAttributes()->getStockItem();
573+
$this->assertEquals(false, $stockItem->getIsInStock());
574+
$stockData = [
575+
'backorders' => 1,
576+
'qty' => $qty,
577+
'is_in_stock' => $stockStatus,
578+
];
579+
$product->setStockData($stockData);
580+
$product->save();
581+
$stockItem = $product->getExtensionAttributes()->getStockItem();
582+
583+
$this->assertEquals($expectedStockStatus, $stockItem->getIsInStock());
584+
}
585+
586+
/**
587+
* DataProvider for the testSaveWithBackordersEnabled()
588+
* @return array
589+
*/
590+
public function productWithBackordersDataProvider(): array
591+
{
592+
return [
593+
[0, 0, false],
594+
[0, 1, true],
595+
[-1, 0, false],
596+
[-1, 1, true],
597+
[1, 1, true],
598+
];
599+
}
555600
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
\Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize();
10+
11+
/** @var \Magento\TestFramework\ObjectManager $objectManager */
12+
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
13+
14+
/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */
15+
$categoryLinkManagement = $objectManager->get(\Magento\Catalog\Api\CategoryLinkManagementInterface::class);
16+
17+
/** @var $product \Magento\Catalog\Model\Product */
18+
$product = $objectManager->create(\Magento\Catalog\Model\Product::class);
19+
$product->isObjectNew(true);
20+
$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
21+
->setId(1)
22+
->setAttributeSetId(4)
23+
->setWebsiteIds([1])
24+
->setName('Simple Product')
25+
->setSku('simple-out-of-stock')
26+
->setPrice(10)
27+
->setWeight(1)
28+
->setShortDescription("Short description")
29+
->setTaxClassId(0)
30+
->setDescription('Description with <b>html tag</b>')
31+
->setMetaTitle('meta title')
32+
->setMetaKeyword('meta keyword')
33+
->setMetaDescription('meta description')
34+
->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
35+
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
36+
->setStockData(
37+
[
38+
'use_config_manage_stock' => 1,
39+
'qty' => 0,
40+
'is_qty_decimal' => 0,
41+
'is_in_stock' => 0,
42+
]
43+
)->setCanSaveCustomOptions(true)
44+
->setHasOptions(true);
45+
46+
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */
47+
$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
48+
$productRepository->save($product);
49+
50+
$categoryLinkManagement->assignProductToCategories(
51+
$product->getSku(),
52+
[2]
53+
);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Magento\Framework\Exception\NoSuchEntityException;
10+
11+
\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize();
12+
13+
/** @var \Magento\Framework\Registry $registry */
14+
$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
15+
16+
$registry->unregister('isSecureArea');
17+
$registry->register('isSecureArea', true);
18+
19+
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
20+
$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
21+
->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
22+
try {
23+
$product = $productRepository->get('simple-out-of-stock', false, null, true);
24+
$productRepository->delete($product);
25+
} catch (NoSuchEntityException $e) {
26+
}
27+
$registry->unregister('isSecureArea');
28+
$registry->register('isSecureArea', false);

dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,4 +2181,49 @@ public function testProductsWithMultipleStoresWhenMediaIsDisabled(): void
21812181
$this->assertTrue($errors->getErrorsCount() === 0);
21822182
$this->assertTrue($this->_model->importData());
21832183
}
2184+
2185+
/**
2186+
* Test that imported product stock status with backorders functionality enabled can be set to 'out of stock'.
2187+
*
2188+
* @magentoDataIsolation enabled
2189+
* @magentoAppIsolation enabled
2190+
*
2191+
* @return void
2192+
*/
2193+
public function testImportWithBackordersEnabled(): void
2194+
{
2195+
$this->importFile('products_to_import_with_backorders_enabled_and_0_qty.csv');
2196+
$product = $this->getProductBySku('simple_new');
2197+
$this->assertFalse($product->getDataByKey('quantity_and_stock_status')['is_in_stock']);
2198+
}
2199+
2200+
/**
2201+
* Import file by providing import filename in parameters.
2202+
*
2203+
* @param string $fileName
2204+
* @return void
2205+
*/
2206+
private function importFile(string $fileName): void
2207+
{
2208+
$filesystem = $this->objectManager->create(\Magento\Framework\Filesystem::class);
2209+
$directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
2210+
$source = $this->objectManager->create(
2211+
\Magento\ImportExport\Model\Import\Source\Csv::class,
2212+
[
2213+
'file' => __DIR__ . '/_files/' . $fileName,
2214+
'directory' => $directory,
2215+
]
2216+
);
2217+
$errors = $this->_model->setParameters(
2218+
[
2219+
'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND,
2220+
'entity' => 'catalog_product',
2221+
\Magento\ImportExport\Model\Import::FIELDS_ENCLOSURE => 1,
2222+
]
2223+
)->setSource($source)->validateData();
2224+
2225+
$this->assertTrue($errors->getErrorsCount() == 0);
2226+
2227+
$this->_model->importData();
2228+
}
21842229
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus
2+
simple_new,,Default,simple,,base,New Product,,,,1,Taxable Goods,"Catalog, Search",10,,,,new-product,New Product,New Product,New Product ,,,,,,,10/20/2015 7:05,10/20/2015 7:05,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,quantity_and_stock_status=In Stock,required_options=1",0,0,1,0,1,0,1,1,10000,1,0,1,1,1,0,1,1,0,0,0,1,,,,,,,,,,,,,

0 commit comments

Comments
 (0)