Skip to content

Commit 0208e43

Browse files
Merge pull request #526 from adobe-commerce-tier-4/PR-Tier4-VK-2025-01-09
PR-Tier4-VK-2025-01-09
2 parents 7f625de + 5259219 commit 0208e43

File tree

4 files changed

+269
-9
lines changed

4 files changed

+269
-9
lines changed

InventoryBundleProduct/Model/GetChidrenSkusByParentIds.php

Lines changed: 25 additions & 3 deletions
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 2021 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -10,6 +10,11 @@
1010
use Magento\Bundle\Model\ResourceModel\Selection\Collection;
1111
use Magento\Bundle\Model\ResourceModel\Selection\CollectionFactory;
1212
use Magento\Bundle\Model\ResourceModel\Selection\Collection\FilterApplier;
13+
use Magento\Catalog\Api\Data\ProductInterface;
14+
use Magento\Framework\App\ObjectManager;
15+
use Magento\Framework\EntityManager\MetadataPool;
16+
use Zend_Db_Expr;
17+
use Zend_Db_Select_Exception;
1318

1419
/**
1520
* Retrieve bundle products selections SKUs.
@@ -33,33 +38,50 @@ class GetChidrenSkusByParentIds
3338
*/
3439
private $batchSize;
3540

41+
/**
42+
* @var MetadataPool
43+
*/
44+
private $metadataPool;
45+
3646
/**
3747
* @param CollectionFactory $selectionCollectionFactory
3848
* @param FilterApplier $selectionCollectionFilterApplier
3949
* @param int $batchSize
50+
* @param MetadataPool|null $metadataPool
4051
*/
4152
public function __construct(
4253
CollectionFactory $selectionCollectionFactory,
4354
FilterApplier $selectionCollectionFilterApplier,
44-
int $batchSize = self::DEFAULT_BATCH_SIZE
55+
int $batchSize = self::DEFAULT_BATCH_SIZE,
56+
?MetadataPool $metadataPool = null
4557
) {
4658
$this->selectionCollectionFactory = $selectionCollectionFactory;
4759
$this->selectionCollectionFilterApplier = $selectionCollectionFilterApplier;
4860
$this->batchSize = $batchSize;
61+
$this->metadataPool = $metadataPool ?:
62+
ObjectManager::getInstance()->get(MetadataPool::class);
4963
}
5064

5165
/**
5266
* Return bundle products selections SKUs indexed by bundle product link ID.
5367
*
5468
* @param array $parentIds
5569
* @return array
70+
* @throws Zend_Db_Select_Exception
5671
*/
5772
public function execute(array $parentIds): array
5873
{
5974
/** @var Collection $collection */
6075
$collection = $this->selectionCollectionFactory->create();
6176
$collection->addFilterByRequiredOptions();
6277
$collection->setFlag('product_children', true);
78+
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)
79+
->getLinkField();
80+
$collection->getSelect()->group('e.' . $linkField);
81+
$collection->getSelect()->columns([
82+
'parent_product_id' =>
83+
new Zend_Db_Expr('GROUP_CONCAT(selection.parent_product_id)')
84+
]);
6385
$this->selectionCollectionFilterApplier->apply(
6486
$collection,
6587
'parent_product_id',

InventoryBundleProduct/Test/Api/BundleProductShipTogetherSaveChildValidationTest.php

Lines changed: 34 additions & 2 deletions
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 2020 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -122,6 +122,38 @@ public function testAddOptionShipmentTypeSeparately(): void
122122
self::assertTrue($result);
123123
}
124124

125+
/**
126+
* Verify, simple product can be updated in bundle product "Ship Together" in case of single source.
127+
*
128+
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/sources.php
129+
* @magentoApiDataFixture Magento_InventoryApi::Test/_files/source_items.php
130+
* @magentoApiDataFixture Magento_InventoryBundleProduct::Test/_files/product_bundle_ship_together.php
131+
*/
132+
public function testUpdateSimpleProductShipmentTypeTogetherSingleSource(): void
133+
{
134+
$bundleProduct = $this->productRepository->get('bundle-ship-together');
135+
$options = $bundleProduct->getExtensionAttributes()->getBundleProductOptions();
136+
$option = current($options);
137+
$simple = $this->productRepository->get('SKU-5');
138+
$simple->setName($simple->getName().'-updated-option');
139+
$simple->setPrice(100);
140+
$this->productRepository->save($simple);
141+
$productLink = current($option->getProductLinks());
142+
$linkedProduct = [
143+
'id' => $productLink->getId(),
144+
'sku' => $simple->getSku(),
145+
'option_id' => $option->getId(),
146+
'qty' => 1,
147+
'position' => 1,
148+
'priceType' => 2,
149+
'price' => $simple->getPrice(),
150+
'is_default' => true,
151+
'can_change_quantity' => 0,
152+
];
153+
$result = $this->saveChild($bundleProduct->getSku(), $linkedProduct);
154+
self::assertTrue($result);
155+
}
156+
125157
/**
126158
* Make Web Api call to save bundle product selection.
127159
*

InventoryCatalogAdminUi/Model/GetQuantityInformationPerSourceBySkus.php

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2018 Adobe
4+
* All Rights Reserved.
55
*/
6+
67
declare(strict_types=1);
78

89
namespace Magento\InventoryCatalogAdminUi\Model;
910

1011
use Magento\Framework\Api\SearchCriteriaBuilderFactory;
1112
use Magento\Framework\Exception\NoSuchEntityException;
13+
use Magento\InventoryApi\Api\Data\SourceInterface;
1214
use Magento\InventoryApi\Api\Data\SourceItemInterface;
1315
use Magento\InventoryApi\Api\SourceItemRepositoryInterface;
1416
use Magento\InventoryApi\Api\SourceRepositoryInterface;
@@ -62,10 +64,16 @@ public function execute(array $skus): array
6264
$searchCriteriaBuilder = $this->searchCriteriaBuilderFactory->create();
6365
$searchCriteria = $searchCriteriaBuilder->addFilter(SourceItemInterface::SKU, $skus, 'in')->create();
6466
$sourceItems = $this->sourceItemRepository->getList($searchCriteria)->getItems();
67+
$sources = $this->getSources($sourceItems);
6568

6669
foreach ($sourceItems as $sourceItem) {
67-
$source = $this->sourceRepository->get($sourceItem->getSourceCode());
68-
$sourceItemsInformation[$sourceItem['sku']][] = [
70+
$source = $sources[$sourceItem->getSourceCode()] ?? null;
71+
if (!$source) {
72+
throw new NoSuchEntityException(
73+
__('Source with code "%value" does not exist.', ['value' => $sourceItem->getSourceCode()])
74+
);
75+
}
76+
$sourceItemsInformation[$sourceItem->getSku()][] = [
6977
SourceItemInterface::SOURCE_CODE => $sourceItem->getSourceCode(),
7078
SourceItemInterface::QUANTITY => $sourceItem->getQuantity(),
7179
'source_name' => $source->getName(),
@@ -75,4 +83,26 @@ public function execute(array $skus): array
7583

7684
return $sourceItemsInformation;
7785
}
86+
87+
/**
88+
* Returns sources by source items.
89+
*
90+
* @param SourceItemInterface[] $sourceItems
91+
* @return SourceInterface[]
92+
*/
93+
private function getSources(array $sourceItems): array
94+
{
95+
$searchCriteriaBuilder = $this->searchCriteriaBuilderFactory->create();
96+
$sourceCodes = [];
97+
foreach ($sourceItems as $sourceItem) {
98+
$sourceCodes[$sourceItem->getSourceCode()] = true;
99+
}
100+
$sourceCodes = array_keys($sourceCodes);
101+
$searchCriteria = $searchCriteriaBuilder->addFilter(SourceInterface::SOURCE_CODE, $sourceCodes, 'in')->create();
102+
$sources = [];
103+
foreach ($this->sourceRepository->getList($searchCriteria)->getItems() as $source) {
104+
$sources[$source->getSourceCode()] = $source;
105+
}
106+
return $sources;
107+
}
78108
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\InventoryCatalogAdminUi\Test\Unit\Model;
9+
10+
use Magento\Framework\Api\SearchCriteria;
11+
use Magento\Framework\Api\SearchCriteriaBuilder;
12+
use Magento\Framework\Api\SearchCriteriaBuilderFactory;
13+
use Magento\InventoryApi\Api\Data\SourceInterface;
14+
use Magento\InventoryApi\Api\Data\SourceItemInterface;
15+
use Magento\InventoryApi\Api\Data\SourceItemSearchResultsInterface;
16+
use Magento\InventoryApi\Api\Data\SourceSearchResultsInterface;
17+
use Magento\InventoryApi\Api\SourceItemRepositoryInterface;
18+
use Magento\InventoryApi\Api\SourceRepositoryInterface;
19+
use Magento\InventoryCatalogAdminUi\Model\GetQuantityInformationPerSourceBySkus;
20+
use PHPUnit\Framework\MockObject\MockObject;
21+
use PHPUnit\Framework\TestCase;
22+
23+
class GetQuantityInformationPerSourceBySkusTest extends TestCase
24+
{
25+
/**
26+
* @var SourceItemRepositoryInterface|MockObject
27+
*/
28+
private $sourceItemRepository;
29+
30+
/**
31+
* @var SearchCriteriaBuilderFactory|MockObject
32+
*/
33+
private $searchCriteriaBuilderFactory;
34+
35+
/**
36+
* @var SourceRepositoryInterface|MockObject
37+
*/
38+
private $sourceRepository;
39+
40+
/**
41+
* @var GetQuantityInformationPerSourceBySkus
42+
*/
43+
private $model;
44+
45+
/**
46+
* @var array[]
47+
*/
48+
private array $sources = [
49+
'source_code1' => [
50+
'getSourceCode' => 'source_code1',
51+
'getName' => 'source_name1',
52+
],
53+
'source_code2' => [
54+
'getSourceCode' => 'source_code2',
55+
'getName' => 'source_name2',
56+
],
57+
];
58+
59+
/**
60+
* @var array[]
61+
*/
62+
private array $sourceItems = [
63+
[
64+
'getSku' => 'sku1',
65+
'getSourceCode' => 'source_code1',
66+
'getQuantity' => 100.00,
67+
'getStatus' => 1,
68+
],
69+
[
70+
'getSku' => 'sku2',
71+
'getSourceCode' => 'source_code1',
72+
'getQuantity' => 200.00,
73+
'getStatus' => 1,
74+
],
75+
[
76+
'getSku' => 'sku2',
77+
'getSourceCode' => 'source_code2',
78+
'getQuantity' => 201.00,
79+
'getStatus' => 1,
80+
],
81+
[
82+
'getSku' => 'sku3',
83+
'getSourceCode' => 'source_code2',
84+
'getQuantity' => 300.00,
85+
'getStatus' => 1,
86+
],
87+
];
88+
89+
protected function setUp(): void
90+
{
91+
parent::setUp();
92+
$this->sourceItemRepository = $this->createMock(SourceItemRepositoryInterface::class);
93+
$this->searchCriteriaBuilderFactory = $this->createMock(SearchCriteriaBuilderFactory::class);
94+
$this->sourceRepository = $this->createMock(SourceRepositoryInterface::class);
95+
$this->model = new GetQuantityInformationPerSourceBySkus(
96+
$this->sourceItemRepository,
97+
$this->searchCriteriaBuilderFactory,
98+
$this->sourceRepository
99+
);
100+
}
101+
102+
public function testExecute(): void
103+
{
104+
$skus = ['sku1', 'sku2', 'sku3'];
105+
$sourceItems[] = $this->createConfiguredMock(SourceItemInterface::class, $this->sourceItems[0]);
106+
$sourceItems[] = $this->createConfiguredMock(SourceItemInterface::class, $this->sourceItems[1]);
107+
$sourceItems[] = $this->createConfiguredMock(SourceItemInterface::class, $this->sourceItems[2]);
108+
$sourceItems[] = $this->createConfiguredMock(SourceItemInterface::class, $this->sourceItems[3]);
109+
$sources[] = $this->createConfiguredMock(SourceInterface::class, $this->sources['source_code1']);
110+
$sources[] = $this->createConfiguredMock(SourceInterface::class, $this->sources['source_code2']);
111+
$searchCriteria1 = $this->createMock(SearchCriteria::class);
112+
$searchCriteria2 = $this->createMock(SearchCriteria::class);
113+
$searchCriteriaBuilder1 = $this->createMock(SearchCriteriaBuilder::class);
114+
$searchCriteriaBuilder2 = $this->createMock(SearchCriteriaBuilder::class);
115+
$searchCriteriaBuilders = [$searchCriteriaBuilder1, $searchCriteriaBuilder2];
116+
$sourceItemSearchResult = $this->createMock(SourceItemSearchResultsInterface::class);
117+
$sourceSearchResult = $this->createMock(SourceSearchResultsInterface::class);
118+
$this->searchCriteriaBuilderFactory->expects($this->exactly(2))
119+
->method('create')
120+
->willReturnCallback(
121+
function () use (&$searchCriteriaBuilders) {
122+
return array_shift($searchCriteriaBuilders);
123+
}
124+
);
125+
126+
$searchCriteriaBuilder1->expects($this->once())
127+
->method('addFilter')
128+
->with(SourceItemInterface::SKU, $skus, 'in')
129+
->willReturnSelf();
130+
$searchCriteriaBuilder1
131+
->expects($this->once())->method('create')
132+
->willReturn($searchCriteria1);
133+
134+
$searchCriteriaBuilder2->expects($this->once())
135+
->method('addFilter')
136+
->with(SourceInterface::SOURCE_CODE, ['source_code1', 'source_code2'], 'in')
137+
->willReturnSelf();
138+
$searchCriteriaBuilder2->expects($this->once())
139+
->method('create')
140+
->willReturn($searchCriteria2);
141+
142+
$sourceItemSearchResult->expects($this->once())
143+
->method('getItems')
144+
->willReturn($sourceItems);
145+
$this->sourceItemRepository->expects($this->once())
146+
->method('getList')
147+
->with($searchCriteria1)
148+
->willReturn($sourceItemSearchResult);
149+
150+
$sourceSearchResult->expects($this->once())
151+
->method('getItems')
152+
->willReturn($sources);
153+
$this->sourceRepository->expects($this->once())
154+
->method('getList')
155+
->with($searchCriteria2)
156+
->willReturn($sourceSearchResult);
157+
158+
$this->assertEquals($this->getExpectedResult($skus), $this->model->execute($skus));
159+
}
160+
161+
private function getExpectedResult(array $skus): array
162+
{
163+
$result = [];
164+
foreach ($this->sourceItems as $sourceItem) {
165+
if (in_array($sourceItem['getSku'], $skus, true)) {
166+
$result[$sourceItem['getSku']][] = [
167+
'source_code' => $sourceItem['getSourceCode'],
168+
'quantity' => $sourceItem['getQuantity'],
169+
'source_name' => $this->sources[$sourceItem['getSourceCode']]['getName'],
170+
'status' => $sourceItem['getStatus'],
171+
];
172+
}
173+
}
174+
return $result;
175+
}
176+
}

0 commit comments

Comments
 (0)