Skip to content

Commit a6e6781

Browse files
committed
Merge remote-tracking branch 'l3/MC-40035' into PR-01-21
2 parents 4c35627 + 2436459 commit a6e6781

File tree

3 files changed

+281
-11
lines changed

3 files changed

+281
-11
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Bundle\Model\Product;
9+
10+
use Magento\Bundle\Api\Data\OptionInterface;
11+
use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository;
12+
use Magento\Bundle\Model\Link;
13+
use Magento\Framework\Exception\InputException;
14+
use Magento\Framework\Exception\NoSuchEntityException;
15+
16+
/**
17+
* Check bundle product option link if exist
18+
*/
19+
class CheckOptionLinkIfExist
20+
{
21+
/**
22+
* @var OptionRepository
23+
*/
24+
private $optionRepository;
25+
26+
/**
27+
* @param OptionRepository $optionRepository
28+
*/
29+
public function __construct(OptionRepository $optionRepository)
30+
{
31+
$this->optionRepository = $optionRepository;
32+
}
33+
34+
/**
35+
* Check if link is already exist in bundle product option
36+
*
37+
* @param string $sku
38+
* @param OptionInterface $optionToDelete
39+
* @param Link $link
40+
* @return bool
41+
* @throws InputException
42+
* @throws NoSuchEntityException
43+
*/
44+
public function execute(string $sku, OptionInterface $optionToDelete, Link $link): bool
45+
{
46+
$isLinkExist = true;
47+
$availableOptions = $this->getAvailableOptionsAfterDelete($sku, $optionToDelete);
48+
$optionLinkIds = $this->getLinkIds($availableOptions);
49+
if (in_array($link->getEntityId(), $optionLinkIds)) {
50+
$isLinkExist = false;
51+
}
52+
return $isLinkExist;
53+
}
54+
55+
/**
56+
* Retrieve bundle product options after delete option
57+
*
58+
* @param string $sku
59+
* @param OptionInterface $optionToDelete
60+
* @return array
61+
* @throws InputException
62+
* @throws NoSuchEntityException
63+
*/
64+
private function getAvailableOptionsAfterDelete(string $sku, OptionInterface $optionToDelete): array
65+
{
66+
$bundleProductOptions = $this->optionRepository->getList($sku);
67+
$options = [];
68+
foreach ($bundleProductOptions as $bundleOption) {
69+
if ($bundleOption->getOptionId() == $optionToDelete->getOptionId()) {
70+
continue;
71+
}
72+
$options[] = $bundleOption;
73+
}
74+
return $options;
75+
}
76+
77+
/**
78+
* Retrieve bundle product link options
79+
*
80+
* @param array $options
81+
* @return array
82+
*/
83+
private function getLinkIds(array $options): array
84+
{
85+
$ids = [];
86+
foreach ($options as $option) {
87+
$links = $option->getProductLinks();
88+
if (!empty($links)) {
89+
foreach ($links as $link) {
90+
$ids[] = $link->getEntityId();
91+
}
92+
}
93+
}
94+
return $ids;
95+
}
96+
}

app/code/Magento/Bundle/Model/Product/SaveHandler.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
use Magento\Catalog\Api\Data\ProductInterface;
1313
use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository;
1414
use Magento\Bundle\Api\ProductLinkManagementInterface;
15+
use Magento\Framework\App\ObjectManager;
1516
use Magento\Framework\EntityManager\MetadataPool;
1617
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
18+
use Magento\Framework\Exception\InputException;
19+
use Magento\Framework\Exception\NoSuchEntityException;
1720

1821
/**
1922
* Bundle product save handler
@@ -40,22 +43,31 @@ class SaveHandler implements ExtensionInterface
4043
*/
4144
private $metadataPool;
4245

46+
/**
47+
* @var CheckOptionLinkIfExist
48+
*/
49+
private $checkOptionLinkIfExist;
50+
4351
/**
4452
* @param OptionRepository $optionRepository
4553
* @param ProductLinkManagementInterface $productLinkManagement
4654
* @param SaveAction $optionSave
4755
* @param MetadataPool $metadataPool
56+
* @param CheckOptionLinkIfExist|null $checkOptionLinkIfExist
4857
*/
4958
public function __construct(
5059
OptionRepository $optionRepository,
5160
ProductLinkManagementInterface $productLinkManagement,
5261
SaveAction $optionSave,
53-
MetadataPool $metadataPool
62+
MetadataPool $metadataPool,
63+
?CheckOptionLinkIfExist $checkOptionLinkIfExist = null
5464
) {
5565
$this->optionRepository = $optionRepository;
5666
$this->productLinkManagement = $productLinkManagement;
5767
$this->optionSave = $optionSave;
5868
$this->metadataPool = $metadataPool;
69+
$this->checkOptionLinkIfExist = $checkOptionLinkIfExist ??
70+
ObjectManager::getInstance()->get(CheckOptionLinkIfExist::class);
5971
}
6072

6173
/**
@@ -105,13 +117,18 @@ public function execute($entity, $arguments = [])
105117
* @param OptionInterface $option
106118
*
107119
* @return void
120+
* @throws InputException
121+
* @throws NoSuchEntityException
108122
*/
109123
protected function removeOptionLinks($entitySku, $option)
110124
{
111125
$links = $option->getProductLinks();
112126
if (!empty($links)) {
113127
foreach ($links as $link) {
114-
$this->productLinkManagement->removeChild($entitySku, $option->getId(), $link->getSku());
128+
$linkCanBeDeleted = $this->checkOptionLinkIfExist->execute($entitySku, $option, $link);
129+
if ($linkCanBeDeleted) {
130+
$this->productLinkManagement->removeChild($entitySku, $option->getId(), $link->getSku());
131+
}
115132
}
116133
}
117134
}

dev/tests/integration/testsuite/Magento/Bundle/Model/Product/SaveHandlerTest.php

Lines changed: 166 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@
77

88
namespace Magento\Bundle\Model\Product;
99

10+
use Magento\Bundle\Api\Data\LinkInterface;
11+
use Magento\Bundle\Api\Data\LinkInterfaceFactory;
12+
use Magento\Bundle\Api\Data\OptionInterfaceFactory;
1013
use Magento\Catalog\Api\ProductRepositoryInterface;
14+
use Magento\Framework\Exception\LocalizedException;
15+
use Magento\Framework\Exception\NoSuchEntityException;
16+
use Magento\Framework\ObjectManagerInterface;
17+
use Magento\Store\Model\Store;
18+
use Magento\TestFramework\Helper\Bootstrap;
19+
use PHPUnit\Framework\TestCase;
1120

1221
/**
1322
* Test class for \Magento\Bundle\Model\Product\SaveHandler
@@ -18,15 +27,15 @@
1827
* @magentoDbIsolation disabled
1928
* @magentoAppIsolation enabled
2029
*/
21-
class SaveHandlerTest extends \PHPUnit\Framework\TestCase
30+
class SaveHandlerTest extends TestCase
2231
{
2332
/**
24-
* @var \Magento\Framework\ObjectManagerInterface
33+
* @var ObjectManagerInterface
2534
*/
2635
private $objectManager;
2736

2837
/**
29-
* @var \Magento\Store\Model\Store
38+
* @var Store
3039
*/
3140
private $store;
3241

@@ -40,21 +49,23 @@ class SaveHandlerTest extends \PHPUnit\Framework\TestCase
4049
*/
4150
protected function setUp(): void
4251
{
43-
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
44-
$this->store = $this->objectManager->create(\Magento\Store\Model\Store::class);
52+
$this->objectManager = Bootstrap::getObjectManager();
53+
$this->store = $this->objectManager->create(Store::class);
4554
/** @var ProductRepositoryInterface $productRepository */
4655
$this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
4756
}
4857

4958
/**
59+
* Test option title on different stores
60+
*
5061
* @return void
62+
* @throws LocalizedException
63+
* @throws NoSuchEntityException
5164
*/
5265
public function testOptionTitlesOnDifferentStores(): void
5366
{
54-
/**
55-
* @var \Magento\Bundle\Model\Product\OptionList $optionList
56-
*/
57-
$optionList = $this->objectManager->create(\Magento\Bundle\Model\Product\OptionList::class);
67+
/** @var OptionList $optionList */
68+
$optionList = $this->objectManager->create(OptionList::class);
5869

5970
$secondStoreId = $this->store->load('fixture_second_store')->getId();
6071
$thirdStoreId = $this->store->load('fixture_third_store')->getId();
@@ -86,4 +97,150 @@ public function testOptionTitlesOnDifferentStores(): void
8697
$options[0]->getTitle()
8798
);
8899
}
100+
101+
/**
102+
* Test option link of the same product
103+
*
104+
* @return void
105+
* @throws LocalizedException
106+
* @throws NoSuchEntityException
107+
*/
108+
public function testOptionLinksOfSameProduct(): void
109+
{
110+
/** @var OptionList $optionList */
111+
$optionList = $this->objectManager->create(OptionList::class);
112+
$product = $this->productRepository->get('bundle-product', true);
113+
114+
//set the first option
115+
$options = $this->setBundleProductOptionData();
116+
$extension = $product->getExtensionAttributes();
117+
$extension->setBundleProductOptions($options);
118+
$product->setExtensionAttributes($extension);
119+
$product->save();
120+
121+
$product = $this->productRepository->get('bundle-product', true);
122+
$options = $optionList->getItems($product);
123+
$this->assertCount(1, $options);
124+
125+
//set the second option with same product
126+
$newOption = $this->setBundleProductOptionData();
127+
array_push($options, current($newOption));
128+
$extension = $product->getExtensionAttributes();
129+
$extension->setBundleProductOptions($options);
130+
$product->setExtensionAttributes($extension);
131+
$product->save();
132+
$this->assertCount(2, $options);
133+
134+
//remove one option and verify the count
135+
array_pop($options);
136+
$extension = $product->getExtensionAttributes();
137+
$extension->setBundleProductOptions($options);
138+
$product->setExtensionAttributes($extension);
139+
$product->save();
140+
141+
$product = $this->productRepository->get('bundle-product', true);
142+
$options = $optionList->getItems($product);
143+
$this->assertCount(1, $options);
144+
}
145+
146+
/**
147+
* Set product option link
148+
*
149+
* @param $bundleLinks
150+
* @param $option
151+
* @return array
152+
* @throws NoSuchEntityException
153+
*/
154+
private function setProductLink($bundleLinks, $option): array
155+
{
156+
$links = [];
157+
$options = [];
158+
if (!empty($bundleLinks)) {
159+
foreach ($bundleLinks as $linkData) {
160+
if (!(bool)$linkData['delete']) {
161+
/** @var LinkInterface $link */
162+
$link = $this->objectManager->create(LinkInterfaceFactory::class)
163+
->create(['data' => $linkData]);
164+
$linkProduct = $this->productRepository->getById($linkData['product_id']);
165+
$link->setSku($linkProduct->getSku());
166+
$link->setQty($linkData['selection_qty']);
167+
$link->setPrice($linkData['selection_price_value']);
168+
if (isset($linkData['selection_can_change_qty'])) {
169+
$link->setCanChangeQuantity($linkData['selection_can_change_qty']);
170+
}
171+
$links[] = $link;
172+
}
173+
}
174+
$option->setProductLinks($links);
175+
$options[] = $option;
176+
}
177+
return $options;
178+
}
179+
180+
/**
181+
* Set product option
182+
*
183+
* @return array
184+
* @throws NoSuchEntityException
185+
*/
186+
private function setProductOption(): array
187+
{
188+
$options = [];
189+
$product = $this->productRepository->get('bundle-product', true);
190+
foreach ($product->getBundleOptionsData() as $optionData) {
191+
if (!(bool)$optionData['delete']) {
192+
$option = $this->objectManager->create(OptionInterfaceFactory::class)
193+
->create(['data' => $optionData]);
194+
$option->setSku($product->getSku());
195+
$option->setOptionId(null);
196+
197+
$bundleLinks = $product->getBundleSelectionsData();
198+
if (!empty($bundleLinks)) {
199+
$options = $this->setProductLink(current($bundleLinks), $option);
200+
}
201+
}
202+
}
203+
return $options;
204+
}
205+
206+
/**
207+
* Set bundle product option data
208+
*
209+
* @return array
210+
* @throws NoSuchEntityException
211+
*/
212+
private function setBundleProductOptionData(): array
213+
{
214+
$options = [];
215+
$product = $this->productRepository->get('bundle-product', true);
216+
$simpleProduct = $this->productRepository->get('simple');
217+
$product->setBundleOptionsData(
218+
[
219+
[
220+
'title' => 'Bundle Product Items',
221+
'default_title' => 'Bundle Product Items',
222+
'type' => 'select', 'required' => 1,
223+
'delete' => '',
224+
],
225+
]
226+
);
227+
$product->setBundleSelectionsData(
228+
[
229+
[
230+
[
231+
'product_id' => $simpleProduct->getId(),
232+
'selection_price_value' => 10,
233+
'selection_qty' => 1,
234+
'selection_can_change_qty' => 1,
235+
'delete' => '',
236+
237+
],
238+
],
239+
]
240+
);
241+
if ($product->getBundleOptionsData()) {
242+
$options = $this->setProductOption();
243+
}
244+
return $options;
245+
}
89246
}

0 commit comments

Comments
 (0)