Skip to content

Commit 77f72b2

Browse files
committed
Merge remote-tracking branch 'origin/MAGETWO-90729' into 2.3-develop-pr20
2 parents a24105d + 3b04ce6 commit 77f72b2

File tree

6 files changed

+228
-40
lines changed

6 files changed

+228
-40
lines changed

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

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Magento\Framework\App\ObjectManager;
1616
use Magento\Framework\Exception\LocalizedException;
1717
use Magento\Framework\Filesystem;
18+
use Magento\Framework\Intl\DateTimeFactory;
1819
use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor;
1920
use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface;
2021
use Magento\Framework\Stdlib\DateTime;
@@ -724,6 +725,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
724725
*/
725726
private $mediaProcessor;
726727

728+
/**
729+
* @var DateTimeFactory
730+
*/
731+
private $dateTimeFactory;
732+
727733
/**
728734
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
729735
* @param \Magento\ImportExport\Helper\Data $importExportData
@@ -767,7 +773,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
767773
* @param ImageTypeProcessor $imageTypeProcessor
768774
* @param MediaGalleryProcessor $mediaProcessor
769775
* @param StockItemImporterInterface|null $stockItemImporter
770-
*
776+
* @param DateTimeFactory $dateTimeFactory
771777
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
772778
*/
773779
public function __construct(
@@ -812,7 +818,8 @@ public function __construct(
812818
CatalogConfig $catalogConfig = null,
813819
ImageTypeProcessor $imageTypeProcessor = null,
814820
MediaGalleryProcessor $mediaProcessor = null,
815-
StockItemImporterInterface $stockItemImporter = null
821+
StockItemImporterInterface $stockItemImporter = null,
822+
DateTimeFactory $dateTimeFactory = null
816823
) {
817824
$this->_eventManager = $eventManager;
818825
$this->stockRegistry = $stockRegistry;
@@ -858,16 +865,14 @@ public function __construct(
858865
$string,
859866
$errorAggregator
860867
);
861-
$this->_optionEntity = isset(
862-
$data['option_entity']
863-
) ? $data['option_entity'] : $optionFactory->create(
864-
['data' => ['product_entity' => $this]]
865-
);
868+
$this->_optionEntity = $data['option_entity'] ??
869+
$optionFactory->create(['data' => ['product_entity' => $this]]);
866870
$this->_initAttributeSets()
867871
->_initTypeModels()
868872
->_initSkus()
869873
->initImagesArrayKeys();
870874
$this->validator->init($this);
875+
$this->dateTimeFactory = $dateTimeFactory ?? ObjectManager::getInstance()->get(DateTimeFactory::class);
871876
}
872877

873878
/**
@@ -2151,40 +2156,8 @@ protected function _saveStockItem()
21512156
$row = [];
21522157
$sku = $rowData[self::COL_SKU];
21532158
if ($this->skuProcessor->getNewSku($sku) !== null) {
2154-
$row['product_id'] = $this->skuProcessor->getNewSku($sku)['entity_id'];
2159+
$row = $this->formatStockDataForRow($rowData);
21552160
$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-
}
21882161
}
21892162

21902163
if (!isset($stockData[$sku])) {
@@ -2875,4 +2848,44 @@ private function getExistingSku($sku)
28752848
{
28762849
return $this->_oldSku[strtolower($sku)];
28772850
}
2851+
2852+
/**
2853+
* Format row data to DB compatible values.
2854+
*
2855+
* @param array $rowData
2856+
* @return array
2857+
*/
2858+
private function formatStockDataForRow(array $rowData): array
2859+
{
2860+
$sku = $rowData[self::COL_SKU];
2861+
$row['product_id'] = $this->skuProcessor->getNewSku($sku)['entity_id'];
2862+
$row['website_id'] = $this->stockConfiguration->getDefaultScopeId();
2863+
$row['stock_id'] = $this->stockRegistry->getStock($row['website_id'])->getStockId();
2864+
2865+
$stockItemDo = $this->stockRegistry->getStockItem($row['product_id'], $row['website_id']);
2866+
$existStockData = $stockItemDo->getData();
2867+
2868+
$row = array_merge(
2869+
$this->defaultStockData,
2870+
array_intersect_key($existStockData, $this->defaultStockData),
2871+
array_intersect_key($rowData, $this->defaultStockData),
2872+
$row
2873+
);
2874+
2875+
if ($this->stockConfiguration->isQty($this->skuProcessor->getNewSku($sku)['type_id'])) {
2876+
$stockItemDo->setData($row);
2877+
$row['is_in_stock'] = isset($row['is_in_stock']) && $stockItemDo->getBackorders()
2878+
? $row['is_in_stock']
2879+
: $this->stockStateProvider->verifyStock($stockItemDo);
2880+
if ($this->stockStateProvider->verifyNotification($stockItemDo)) {
2881+
$date = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC'));
2882+
$row['low_stock_date'] = $date->format(DateTime::DATETIME_PHP_FORMAT);
2883+
}
2884+
$row['stock_status_changed_auto'] = (int)!$this->stockStateProvider->verifyStock($stockItemDo);
2885+
} else {
2886+
$row['qty'] = 0;
2887+
}
2888+
2889+
return $row;
2890+
}
28782891
}

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+
->setAttributeSetId($product->getDefaultAttributeSetId())
22+
->setWebsiteIds([1])
23+
->setName('Simple Product')
24+
->setSku('simple-out-of-stock')
25+
->setPrice(10)
26+
->setWeight(1)
27+
->setShortDescription("Short description")
28+
->setTaxClassId(0)
29+
->setDescription('Description with <b>html tag</b>')
30+
->setMetaTitle('meta title')
31+
->setMetaKeyword('meta keyword')
32+
->setMetaDescription('meta description')
33+
->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
34+
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
35+
->setStockData(
36+
[
37+
'use_config_manage_stock' => 1,
38+
'qty' => 0,
39+
'is_qty_decimal' => 0,
40+
'is_in_stock' => 0,
41+
]
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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,4 +2181,51 @@ 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+
* @magentoDbIsolation 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+
)
2224+
->setSource($source)
2225+
->validateData();
2226+
2227+
$this->assertTrue($errors->getErrorsCount() == 0);
2228+
2229+
$this->_model->importData();
2230+
}
21842231
}
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)