Skip to content

Commit 5b21b7a

Browse files
Merge pull request #514 from adobe-commerce-tier-4/PR-11-19-2024
[Support Tier-4 chittima] 11-19-2024 Regular delivery of bugfixes and improvements
2 parents edc2c37 + 3839ade commit 5b21b7a

File tree

7 files changed

+154
-89
lines changed

7 files changed

+154
-89
lines changed

InventoryCatalog/Plugin/Catalog/Model/ResourceModel/Product/CollectionPlugin.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2022 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -82,7 +82,7 @@ private function applyOutOfStockSortOrders(Collection $collection): void
8282
$collection->setFlag('is_sorted_by_oos', true);
8383

8484
if ($this->isOutOfStockBottom() && $this->sortableBySaleabilityProvider->isSortableBySaleability()) {
85-
$collection->setOrder(SortableBySaleabilityInterface::IS_OUT_OF_STOCK, Select::SQL_DESC);
85+
$collection->setOrder(SortableBySaleabilityInterface::IS_OUT_OF_STOCK, Select::SQL_ASC);
8686
}
8787
}
8888
$collection->setFlag('is_processing', false);

InventoryElasticsearch/Plugin/Model/Adapter/BatchDataMapper/ProductDataMapperPlugin.php

+14-38
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,33 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2022 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

88
namespace Magento\InventoryElasticsearch\Plugin\Model\Adapter\BatchDataMapper;
99

1010
use Magento\Elasticsearch\Model\Adapter\BatchDataMapper\ProductDataMapper;
1111
use Magento\Framework\Exception\NoSuchEntityException;
12+
use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface;
13+
use Magento\InventorySalesApi\Model\GetStockItemsDataInterface;
1214
use Magento\InventorySalesApi\Model\StockByWebsiteIdResolverInterface;
1315
use Magento\Store\Api\StoreRepositoryInterface;
14-
use Magento\InventorySalesApi\Model\GetStockItemDataInterface;
1516

1617
class ProductDataMapperPlugin
1718
{
18-
/**
19-
* @var StockByWebsiteIdResolverInterface
20-
*/
21-
private $stockByWebsiteIdResolver;
22-
23-
/**
24-
* @var StoreRepositoryInterface
25-
*/
26-
private $storeRepository;
27-
28-
/**
29-
* @var GetStockItemDataInterface
30-
*/
31-
private $getStockItemData;
32-
3319
/**
3420
* @param StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver
3521
* @param StoreRepositoryInterface $storeRepository
36-
* @param GetStockItemDataInterface $getStockItemData
22+
* @param GetStockItemsDataInterface $getStockItemsData
23+
* @param GetSkusByProductIdsInterface $getSkusByProductIds
3724
*/
3825
public function __construct(
39-
StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver,
40-
StoreRepositoryInterface $storeRepository,
41-
GetStockItemDataInterface $getStockItemData
26+
private readonly StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver,
27+
private readonly StoreRepositoryInterface $storeRepository,
28+
private readonly GetStockItemsDataInterface $getStockItemsData,
29+
private readonly GetSkusByProductIdsInterface $getSkusByProductIds
4230
) {
43-
$this->stockByWebsiteIdResolver = $stockByWebsiteIdResolver;
44-
$this->storeRepository = $storeRepository;
45-
$this->getStockItemData = $getStockItemData;
4631
}
4732

4833
/**
@@ -64,20 +49,11 @@ public function afterMap(
6449
): array {
6550
$store = $this->storeRepository->getById($storeId);
6651
$stock = $this->stockByWebsiteIdResolver->execute((int)$store->getWebsiteId());
67-
52+
$skus = $this->getSkusByProductIds->execute(array_keys($documents));
53+
$stockItems = $this->getStockItemsData->execute(array_values($skus), $stock->getStockId());
6854
foreach ($documents as $productId => $document) {
69-
$sku = $document['sku'] ?? '';
70-
if (!$sku) {
71-
$document['is_out_of_stock'] = 1;
72-
} else {
73-
try {
74-
$stockItemData = $this->getStockItemData->execute($sku, $stock->getStockId());
75-
} catch (NoSuchEntityException $e) {
76-
$stockItemData = null;
77-
}
78-
$document['is_out_of_stock'] = null !== $stockItemData
79-
? (int)$stockItemData[GetStockItemDataInterface::IS_SALABLE] : 1;
80-
}
55+
$sku = $skus[$productId];
56+
$document['is_out_of_stock'] = (int)!($stockItems[$sku][GetStockItemsDataInterface::IS_SALABLE] ?? 0);
8157
$documents[$productId] = $document;
8258
}
8359

InventoryElasticsearch/Test/Integration/Model/Adapter/BatchDataMapper/ProductDataMapperTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2022 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -94,8 +94,8 @@ public function testMapSelectAttributeWithDifferentStoreLabels(): void
9494
'select_attribute_value' => 'Table_fixture_second_store'
9595
],
9696
];
97-
$defaultStoreMap[$productId] += ['is_out_of_stock' => 1];
98-
$secondStoreMap[$productId] += ['is_out_of_stock' => 1];
97+
$defaultStoreMap[$productId] += ['is_out_of_stock' => 0];
98+
$secondStoreMap[$productId] += ['is_out_of_stock' => 0];
9999

100100
$data = [
101101
$productId => [

InventoryElasticsearch/Test/Unit/Plugin/Model/Adapter/BatchDataMapper/ProductDataMapperPluginTest.php

+42-42
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2022 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -10,11 +10,12 @@
1010
use Magento\Elasticsearch\Model\Adapter\BatchDataMapper\ProductDataMapper;
1111
use Magento\Framework\Exception\LocalizedException;
1212
use Magento\Framework\Exception\NoSuchEntityException;
13-
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
1413
use Magento\InventoryApi\Api\Data\StockInterface;
14+
use Magento\InventoryCatalog\Model\GetSkusByProductIds;
1515
use Magento\InventoryElasticsearch\Plugin\Model\Adapter\BatchDataMapper\ProductDataMapperPlugin;
16-
use Magento\InventorySalesApi\Model\GetStockItemDataInterface;
16+
use Magento\InventorySalesApi\Model\GetStockItemsDataInterface;
1717
use Magento\InventorySalesApi\Model\StockByWebsiteIdResolverInterface;
18+
use Magento\Store\Api\Data\StoreInterface;
1819
use Magento\Store\Api\StoreRepositoryInterface;
1920
use PHPUnit\Framework\MockObject\MockObject;
2021
use PHPUnit\Framework\TestCase;
@@ -40,35 +41,35 @@ class ProductDataMapperPluginTest extends TestCase
4041
private $storeRepositoryMock;
4142

4243
/**
43-
* @var GetStockItemDataInterface|MockObject
44+
* @var GetStockItemsDataInterface|MockObject
4445
*/
45-
private $getStockItemDataMock;
46+
private $getStockItemsDataMock;
4647

4748
/**
4849
* @var ProductDataMapper|MockObject
4950
*/
5051
private $productDataMapperMock;
5152

53+
/**
54+
* @var GetSkusByProductIds|MockObject
55+
*/
56+
private $getSkusByProductIdsMock;
57+
5258
/**
5359
* @inheirtDoc
5460
*/
5561
protected function setUp(): void
5662
{
57-
$this->stockByWebsiteIdResolverMock = $this->getMockForAbstractClass(StockByWebsiteIdResolverInterface::class);
58-
$this->storeRepositoryMock = $this->getMockBuilder(StoreRepositoryInterface::class)
59-
->disableOriginalConstructor()
60-
->onlyMethods(['getById'])
61-
->addMethods(['getWebsiteId'])
62-
->getMockForAbstractClass();
63-
$this->getStockItemDataMock = $this->createMock(GetStockItemDataInterface::class);
63+
$this->stockByWebsiteIdResolverMock = $this->createMock(StockByWebsiteIdResolverInterface::class);
64+
$this->storeRepositoryMock = $this->createMock(StoreRepositoryInterface::class);
65+
$this->getStockItemsDataMock = $this->createMock(GetStockItemsDataInterface::class);
6466
$this->productDataMapperMock = $this->createMock(ProductDataMapper::class);
65-
$this->plugin = (new ObjectManager($this))->getObject(
66-
ProductDataMapperPlugin::class,
67-
[
68-
'stockByWebsiteIdResolver' => $this->stockByWebsiteIdResolverMock,
69-
'storeRepository' => $this->storeRepositoryMock,
70-
'getStockItemData' => $this->getStockItemDataMock
71-
]
67+
$this->getSkusByProductIdsMock = $this->createMock(GetSkusByProductIds::class);
68+
$this->plugin = new ProductDataMapperPlugin(
69+
$this->stockByWebsiteIdResolverMock,
70+
$this->storeRepositoryMock,
71+
$this->getStockItemsDataMock,
72+
$this->getSkusByProductIdsMock
7273
);
7374
}
7475

@@ -85,43 +86,42 @@ protected function setUp(): void
8586
*/
8687
public function testAfterMap(int $storeId, int $websiteId, int $stockId, int $salability): void
8788
{
89+
$productId = 123;
8890
$sku = '24-MB01';
89-
$attribute = ['is_out_of_stock' => $salability];
91+
$attribute = ['is_out_of_stock' => (int)!$salability];
9092
$documents = [
91-
1 => [
93+
$productId => [
9294
'store_id' => $storeId,
93-
'sku' => $sku,
94-
'status' => $salability
95+
'status' => 1,
9596
],
9697
];
97-
$expectedResult[1] = array_merge($documents[1], $attribute);
98+
$expectedResult = [$productId => array_merge($documents[$productId], $attribute)];
9899

99-
$this->storeRepositoryMock
100-
->expects($this->once())
100+
$storeMock = $this->createMock(StoreInterface::class);
101+
$this->storeRepositoryMock->expects(self::once())
101102
->method('getById')
102103
->with($storeId)
103-
->willReturnSelf();
104-
$this->storeRepositoryMock
105-
->expects($this->once())
104+
->willReturn($storeMock);
105+
$storeMock->expects(self::atLeastOnce())
106106
->method('getWebsiteId')
107107
->willReturn($websiteId);
108108

109-
$stock = $this->getMockForAbstractClass(StockInterface::class);
110-
$stock->method('getStockId')
109+
$stock = $this->createMock(StockInterface::class);
110+
$stock->expects(self::atLeastOnce())
111+
->method('getStockId')
111112
->willReturn($stockId);
112-
$this->stockByWebsiteIdResolverMock
113+
$this->stockByWebsiteIdResolverMock->expects(self::once())
113114
->method('execute')
115+
->with($websiteId)
114116
->willReturn($stock);
115-
116-
$this->getStockItemDataMock->expects($this->atLeastOnce())
117+
$this->getSkusByProductIdsMock->expects(self::once())
118+
->method('execute')
119+
->with([$productId])
120+
->willReturn([$productId => $sku]);
121+
$this->getStockItemsDataMock->expects(self::once())
117122
->method('execute')
118-
->willReturnCallback(
119-
function ($sku) use ($salability) {
120-
return isset($sku)
121-
? ['is_salable' => $salability]
122-
: null;
123-
}
124-
);
123+
->with([$sku], $stockId)
124+
->willReturn([$sku => ['is_salable' => $salability]]);
125125

126126
$this->assertSame(
127127
$expectedResult,

InventoryImportExport/Model/Import/Validator/QtyValidator.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2017 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -39,6 +39,8 @@ public function validate(array $rowData, int $rowNumber)
3939

4040
if (!isset($rowData[Sources::COL_QTY])) {
4141
$errors[] = __('Missing required column "%column"', ['column' => Sources::COL_QTY]);
42+
} elseif (!is_numeric($rowData[Sources::COL_QTY])) {
43+
$errors[] = __('"%column" contains incorrect value', ['column' => Sources::COL_QTY]);
4244
}
4345

4446
return $this->validationResultFactory->create(['errors' => $errors]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\InventoryImportExport\Test\Unit\Model\Import\Validator;
9+
10+
use Magento\Framework\Validation\ValidationResult;
11+
use Magento\Framework\Validation\ValidationResultFactory;
12+
use Magento\InventoryImportExport\Model\Import\Sources;
13+
use Magento\InventoryImportExport\Model\Import\Validator\QtyValidator;
14+
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\MockObject\MockObject;
16+
use PHPUnit\Framework\TestCase;
17+
18+
class QtyValidatorTest extends TestCase
19+
{
20+
/**
21+
* @var MockObject|null
22+
*/
23+
private ?MockObject $validationResultFactory;
24+
25+
/**
26+
* @var QtyValidator|null
27+
*/
28+
private ?QtyValidator $model;
29+
30+
protected function setUp(): void
31+
{
32+
parent::setUp();
33+
$this->validationResultFactory = $this->createMock(ValidationResultFactory::class);
34+
$this->model = new QtyValidator($this->validationResultFactory);
35+
}
36+
37+
#[DataProvider('validateDataProvider')]
38+
public function testValidate(array $data, array $errors = []): void
39+
{
40+
$rowNumber = 1;
41+
$result = new ValidationResult([]);
42+
$this->validationResultFactory
43+
->expects($this->once())->method('create')
44+
->with(['errors' => $errors])
45+
->willReturn($result);
46+
$this->assertSame($result, $this->model->validate($data, $rowNumber));
47+
}
48+
49+
public static function validateDataProvider(): array
50+
{
51+
return [
52+
[
53+
[Sources::COL_QTY => 1,],
54+
],
55+
[
56+
[Sources::COL_QTY => 0,],
57+
],
58+
[
59+
[Sources::COL_QTY => -1,],
60+
],
61+
[
62+
[Sources::COL_QTY => 0.1,],
63+
],
64+
[
65+
[Sources::COL_QTY => '1',],
66+
],
67+
[
68+
[Sources::COL_QTY => '0',],
69+
],
70+
[
71+
[Sources::COL_QTY => '-1',],
72+
],
73+
[
74+
[Sources::COL_QTY => '0.1',],
75+
],
76+
[
77+
[Sources::COL_QTY => 'abc',],
78+
[__('"%column" contains incorrect value', ['column' => Sources::COL_QTY])],
79+
],
80+
[
81+
['qty' => 1,],
82+
[__('Missing required column "%column"', ['column' => Sources::COL_QTY])],
83+
],
84+
];
85+
}
86+
}

InventoryImportExport/i18n/en_US.csv

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
"Missing required column ""%column""","Missing required column ""%column"""
1111
"Source code ""%code"" does not exists","Source code ""%code"" does not exists"
1212
"Row Validator must implement %interface.","Row Validator must implement %interface."
13+
"""%column"" contains incorrect value","""%column"" contains incorrect value"

0 commit comments

Comments
 (0)