Skip to content

Commit 15015fa

Browse files
ENGCOM-7141: Fixes #12584 Bundle Item price cannot differ per website #27315
- Merge Pull Request #27315 from rain2o/magento2:bugfix/issue-12584 - Merged commits: 1. b25a34f 2. fc9559e 3. a59b0e8 4. 8cca552 5. 49c261e 6. 659beb2 7. f75a78b 8. fd40a7b 9. d7f283c 10. 56ef44b 11. b5c9757 12. 9f622ee 13. 64db19a 14. dc7cc2c 15. ed0f987 16. ac37ae1 17. 975c0c1 18. 171c54d 19. a2a9cba 20. ea54519 21. 377e070 22. 605fb88 23. 6cc13cc 24. 88ea963 25. dc3226b 26. 7da12c1 27. c57d4d0 28. f6bb216 29. 27627ca 30. b00b423 31. eb5e30c
2 parents f6e06ec + eb5e30c commit 15015fa

File tree

10 files changed

+315
-22
lines changed

10 files changed

+315
-22
lines changed

app/code/Magento/Bundle/Model/LinkManagement.php

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
use Magento\Catalog\Api\ProductRepositoryInterface;
2020
use Magento\Catalog\Model\Product;
2121
use Magento\Framework\Api\DataObjectHelper;
22+
use Magento\Framework\EntityManager\MetadataPool;
2223
use Magento\Framework\Exception\CouldNotSaveException;
2324
use Magento\Framework\Exception\InputException;
24-
use Magento\Framework\EntityManager\MetadataPool;
2525
use Magento\Framework\Exception\NoSuchEntityException;
2626
use Magento\Store\Model\StoreManagerInterface;
2727

@@ -173,12 +173,11 @@ public function saveChild(
173173
)
174174
);
175175
}
176-
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
177-
$selectionModel = $this->mapProductLinkToSelectionModel(
176+
$selectionModel = $this->mapProductLinkToBundleSelectionModel(
178177
$selectionModel,
179178
$linkedProduct,
180-
$linkProductModel->getId(),
181-
$product->getData($linkField)
179+
$product,
180+
(int)$linkProductModel->getId()
182181
);
183182

184183
try {
@@ -202,6 +201,7 @@ public function saveChild(
202201
*
203202
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
204203
* @SuppressWarnings(PHPMD.NPathComplexity)
204+
* @deprecated use mapProductLinkToBundleSelectionModel
205205
*/
206206
protected function mapProductLinkToSelectionModel(
207207
Selection $selectionModel,
@@ -239,6 +239,55 @@ protected function mapProductLinkToSelectionModel(
239239
return $selectionModel;
240240
}
241241

242+
/**
243+
* Fill selection model with product link data.
244+
*
245+
* @param Selection $selectionModel
246+
* @param LinkInterface $productLink
247+
* @param ProductInterface $parentProduct
248+
* @param int $linkedProductId
249+
* @param string $linkField
250+
* @return Selection
251+
* @throws NoSuchEntityException
252+
*/
253+
private function mapProductLinkToBundleSelectionModel(
254+
Selection $selectionModel,
255+
LinkInterface $productLink,
256+
ProductInterface $parentProduct,
257+
int $linkedProductId
258+
): Selection {
259+
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
260+
$selectionModel->setProductId($linkedProductId);
261+
$selectionModel->setParentProductId($parentProduct->getData($linkField));
262+
if ($productLink->getSelectionId() !== null) {
263+
$selectionModel->setSelectionId($productLink->getSelectionId());
264+
}
265+
if ($productLink->getOptionId() !== null) {
266+
$selectionModel->setOptionId($productLink->getOptionId());
267+
}
268+
if ($productLink->getPosition() !== null) {
269+
$selectionModel->setPosition($productLink->getPosition());
270+
}
271+
if ($productLink->getQty() !== null) {
272+
$selectionModel->setSelectionQty($productLink->getQty());
273+
}
274+
if ($productLink->getPriceType() !== null) {
275+
$selectionModel->setSelectionPriceType($productLink->getPriceType());
276+
}
277+
if ($productLink->getPrice() !== null) {
278+
$selectionModel->setSelectionPriceValue($productLink->getPrice());
279+
}
280+
if ($productLink->getCanChangeQuantity() !== null) {
281+
$selectionModel->setSelectionCanChangeQty($productLink->getCanChangeQuantity());
282+
}
283+
if ($productLink->getIsDefault() !== null) {
284+
$selectionModel->setIsDefault($productLink->getIsDefault());
285+
}
286+
$selectionModel->setWebsiteId((int)$this->storeManager->getStore($parentProduct->getStoreId())->getWebsiteId());
287+
288+
return $selectionModel;
289+
}
290+
242291
/**
243292
* @inheritDoc
244293
*
@@ -302,12 +351,13 @@ public function addChild(
302351
}
303352

304353
$selectionModel = $this->bundleSelection->create();
305-
$selectionModel = $this->mapProductLinkToSelectionModel(
354+
$selectionModel = $this->mapProductLinkToBundleSelectionModel(
306355
$selectionModel,
307356
$linkedProduct,
308-
$linkProductModel->getEntityId(),
309-
$product->getData($linkField)
357+
$product,
358+
(int)$linkProductModel->getEntityId()
310359
);
360+
311361
$selectionModel->setOptionId($optionId);
312362

313363
try {

app/code/Magento/Bundle/Model/Option/SaveAction.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77

88
namespace Magento\Bundle\Model\Option;
99

10+
use Magento\Bundle\Api\Data\LinkInterface;
1011
use Magento\Bundle\Api\Data\OptionInterface;
1112
use Magento\Bundle\Model\ResourceModel\Option;
1213
use Magento\Catalog\Api\Data\ProductInterface;
14+
use Magento\Framework\App\ObjectManager;
1315
use Magento\Framework\EntityManager\MetadataPool;
1416
use Magento\Framework\Exception\CouldNotSaveException;
1517
use Magento\Bundle\Model\Product\Type;
1618
use Magento\Bundle\Api\ProductLinkManagementInterface;
19+
use Magento\Framework\Exception\NoSuchEntityException;
20+
use Magento\Store\Model\StoreManagerInterface;
1721

1822
/**
1923
* Encapsulates logic for saving a bundle option, including coalescing the parent product's data.
@@ -45,12 +49,14 @@ class SaveAction
4549
* @param MetadataPool $metadataPool
4650
* @param Type $type
4751
* @param ProductLinkManagementInterface $linkManagement
52+
* @param StoreManagerInterface|null $storeManager
4853
*/
4954
public function __construct(
5055
Option $optionResource,
5156
MetadataPool $metadataPool,
5257
Type $type,
53-
ProductLinkManagementInterface $linkManagement
58+
ProductLinkManagementInterface $linkManagement,
59+
?StoreManagerInterface $storeManager = null
5460
) {
5561
$this->optionResource = $optionResource;
5662
$this->metadataPool = $metadataPool;
@@ -69,7 +75,7 @@ public function __construct(
6975
*/
7076
public function save(ProductInterface $bundleProduct, OptionInterface $option)
7177
{
72-
$metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
78+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
7379

7480
$option->setStoreId($bundleProduct->getStoreId());
7581
$parentId = $bundleProduct->getData($metadata->getLinkField());
@@ -108,7 +114,7 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option)
108114
throw new CouldNotSaveException(__("The option couldn't be saved."), $e);
109115
}
110116

111-
/** @var \Magento\Bundle\Api\Data\LinkInterface $linkedProduct */
117+
/** @var LinkInterface $linkedProduct */
112118
foreach ($linksToAdd as $linkedProduct) {
113119
$this->linkManagement->addChild($bundleProduct, $option->getOptionId(), $linkedProduct);
114120
}
@@ -121,8 +127,8 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option)
121127
/**
122128
* Update option selections
123129
*
124-
* @param \Magento\Catalog\Api\Data\ProductInterface $product
125-
* @param \Magento\Bundle\Api\Data\OptionInterface $option
130+
* @param ProductInterface $product
131+
* @param OptionInterface $option
126132
* @return void
127133
*/
128134
private function updateOptionSelection(ProductInterface $product, OptionInterface $option)
@@ -141,7 +147,7 @@ private function updateOptionSelection(ProductInterface $product, OptionInterfac
141147
$linksToUpdate[] = $productLink;
142148
}
143149
}
144-
/** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToDelete */
150+
/** @var LinkInterface[] $linksToDelete */
145151
$linksToDelete = $this->compareLinks($existingLinks, $linksToUpdate);
146152
}
147153
foreach ($linksToUpdate as $linkedProduct) {
@@ -162,8 +168,8 @@ private function updateOptionSelection(ProductInterface $product, OptionInterfac
162168
/**
163169
* Compute the difference between given arrays.
164170
*
165-
* @param \Magento\Bundle\Api\Data\LinkInterface[] $firstArray
166-
* @param \Magento\Bundle\Api\Data\LinkInterface[] $secondArray
171+
* @param LinkInterface[] $firstArray
172+
* @param LinkInterface[] $secondArray
167173
*
168174
* @return array
169175
*/

app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,42 @@ private function getBaseBundleSelectionPriceSelect(): Select
456456
return $select;
457457
}
458458

459+
/**
460+
* Get base select for bundle selection price update
461+
*
462+
* @return Select
463+
* @throws \Exception
464+
*/
465+
private function getBaseBundleSelectionPriceUpdateSelect(): Select
466+
{
467+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
468+
$linkField = $metadata->getLinkField();
469+
$bundleSelectionTable = $this->getBundleSelectionTable();
470+
471+
$select = $this->getConnection()->select()
472+
->join(
473+
['i' => $this->getBundlePriceTable()],
474+
"i.entity_id = $bundleSelectionTable.entity_id
475+
AND i.customer_group_id = $bundleSelectionTable.customer_group_id
476+
AND i.website_id = $bundleSelectionTable.website_id",
477+
[]
478+
)->join(
479+
['parent_product' => $this->getTable('catalog_product_entity')],
480+
'parent_product.entity_id = i.entity_id',
481+
[]
482+
)->join(
483+
['bo' => $this->getTable('catalog_product_bundle_option')],
484+
"bo.parent_id = parent_product.$linkField AND bo.option_id = $bundleSelectionTable.option_id",
485+
['option_id']
486+
)->join(
487+
['bs' => $this->getTable('catalog_product_bundle_selection')],
488+
"bs.option_id = bo.option_id AND bs.selection_id = $bundleSelectionTable.selection_id",
489+
['selection_id']
490+
);
491+
492+
return $select;
493+
}
494+
459495
/**
460496
* Apply selections price for fixed bundles
461497
*
@@ -499,7 +535,7 @@ private function applyFixedBundleSelectionPrice()
499535
]
500536
);
501537

502-
$select = $this->getBaseBundleSelectionPriceSelect();
538+
$select = $this->getBaseBundleSelectionPriceUpdateSelect();
503539
$select->joinInner(
504540
['bsp' => $this->getTable('catalog_product_bundle_selection_price')],
505541
'bs.selection_id = bsp.selection_id AND bsp.website_id = i.website_id',

app/code/Magento/Bundle/Model/Selection.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
* @method \Magento\Bundle\Model\Selection setPosition(int $value)
2121
* @method int getIsDefault()
2222
* @method \Magento\Bundle\Model\Selection setIsDefault(int $value)
23+
* @method int getWebsiteId()
24+
* @method \Magento\Bundle\Model\Selection setWebsiteId(int $value)
2325
* @method int getSelectionPriceType()
2426
* @method \Magento\Bundle\Model\Selection setSelectionPriceType(int $value)
2527
* @method float getSelectionPriceValue()
@@ -74,18 +76,35 @@ protected function _construct()
7476
/**
7577
* Processing object before save data
7678
*
79+
* @return void
80+
*/
81+
public function beforeSave()
82+
{
83+
if (!$this->_catalogData->isPriceGlobal() && $this->getWebsiteId()) {
84+
$this->setData('tmp_selection_price_value', $this->getSelectionPriceValue());
85+
$this->setSelectionPriceValue($this->getOrigData('selection_price_value'));
86+
}
87+
parent::beforeSave();
88+
}
89+
90+
/**
91+
* Processing object after save data
92+
*
7793
* @return $this
7894
*/
7995
public function afterSave()
8096
{
8197
if (!$this->_catalogData->isPriceGlobal() && $this->getWebsiteId()) {
98+
if (null !== $this->getData('tmp_selection_price_value')) {
99+
$this->setSelectionPriceValue($this->getData('tmp_selection_price_value'));
100+
}
82101
$this->getResource()->saveSelectionPrice($this);
83102

84103
if (!$this->getDefaultPriceScope()) {
85104
$this->unsSelectionPriceValue();
86105
$this->unsSelectionPriceType();
87106
}
88107
}
89-
parent::afterSave();
108+
return parent::afterSave();
90109
}
91110
}

app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,13 @@
1717
<data key="price_type">1</data>
1818
<data key="can_change_quantity">1</data>
1919
</entity>
20+
<entity name="ApiBundleLinkFixed" type="bundle_link">
21+
<var key="option_id" entityKey="return" entityType="bundle_option"/>
22+
<var key="sku" entityKey="sku" entityType="product"/>
23+
<data key="qty">1</data>
24+
<data key="is_default">1</data>
25+
<data key="price">30</data>
26+
<data key="price_type">0</data>
27+
<data key="can_change_quantity">1</data>
28+
</entity>
2029
</entities>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="StorefrontCheckBundleProductTwoWebsiteDifferentPriceOptionTest">
12+
<annotations>
13+
<title value="Verify bundle item price different websites."/>
14+
<stories value="Github issue: #12584 Bundle Item price cannot differ per website"/>
15+
<description value="Verify bundle item price different websites. Change bundle item price on second website."/>
16+
<features value="Bundle"/>
17+
<severity value="MAJOR"/>
18+
<group value="bundle"/>
19+
</annotations>
20+
<before>
21+
<magentoCLI command="config:set {{WebsiteCatalogPriceScopeConfigData.path}} {{WebsiteCatalogPriceScopeConfigData.value}}" stepKey="setPriceScopeWebsite"/>
22+
<actionGroup ref="AdminLoginActionGroup" stepKey="logInAsAdmin"/>
23+
24+
<actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite">
25+
<argument name="newWebsiteName" value="{{customWebsite.name}}"/>
26+
<argument name="websiteCode" value="{{customWebsite.code}}"/>
27+
</actionGroup>
28+
<actionGroup ref="CreateCustomStoreActionGroup" stepKey="createCustomStoreGroup">
29+
<argument name="website" value="{{customWebsite.name}}"/>
30+
<argument name="store" value="{{customStoreGroup.name}}"/>
31+
<argument name="rootCategory" value="Default Category"/>
32+
</actionGroup>
33+
<actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView">
34+
<argument name="StoreGroup" value="customStoreGroup"/>
35+
<argument name="customStore" value="customStore"/>
36+
</actionGroup>
37+
38+
<createData entity="SimpleProduct2" stepKey="simpleProduct"/>
39+
<createData entity="ApiFixedBundleProduct" stepKey="createBundleProduct" />
40+
<createData entity="CheckboxOption" stepKey="createBundleOption">
41+
<requiredEntity createDataKey="createBundleProduct"/>
42+
</createData>
43+
<createData entity="ApiBundleLinkFixed" stepKey="linkOptionToProduct">
44+
<requiredEntity createDataKey="createBundleProduct"/>
45+
<requiredEntity createDataKey="createBundleOption"/>
46+
<requiredEntity createDataKey="simpleProduct"/>
47+
</createData>
48+
</before>
49+
<after>
50+
<magentoCLI command="config:set {{GlobalCatalogPriceScopeConfigData.path}} {{GlobalCatalogPriceScopeConfigData.value}}" stepKey="setPriceScopeGlobal"/>
51+
52+
<deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/>
53+
<deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/>
54+
55+
<actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite">
56+
<argument name="websiteName" value="{{customWebsite.name}}"/>
57+
</actionGroup>
58+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>
59+
60+
<actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex">
61+
<argument name="indices" value=""/>
62+
</actionGroup>
63+
<actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanFullPageCache">
64+
<argument name="tags" value="config full_page"/>
65+
</actionGroup>
66+
</after>
67+
68+
<actionGroup ref="NavigateToCreatedProductEditPageActionGroup" stepKey="openEditBundleProduct">
69+
<argument name="product" value="$$createBundleProduct$$"/>
70+
</actionGroup>
71+
72+
<actionGroup ref="AdminAssignProductInWebsiteActionGroup" stepKey="selectProductInWebsites">
73+
<argument name="website" value="{{customWebsite.name}}"/>
74+
</actionGroup>
75+
<actionGroup ref="SaveProductFormActionGroup" stepKey="clickSaveButton"/>
76+
<actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="SwitchNewStoreView">
77+
<argument name="storeViewName" value="{{customStore.name}}"/>
78+
</actionGroup>
79+
80+
<fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYPrice('0', '0')}}" userInput="100" stepKey="fillBundleOption1Price"/>
81+
82+
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveNewPrice"/>
83+
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage">
84+
<argument name="productUrl" value="$$createBundleProduct.custom_attributes[url_key]$$"/>
85+
</actionGroup>
86+
87+
<click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomizeAndAddToCart"/>
88+
89+
<grabTextFrom selector="{{StorefrontBundledSection.bundleProductsPrice}}" stepKey="grabPriceText"/>
90+
<assertEquals stepKey="assertPriceText">
91+
<expectedResult type="string">$31.23</expectedResult>
92+
<actualResult type="variable">$grabPriceText</actualResult>
93+
</assertEquals>
94+
95+
</test>
96+
</tests>

0 commit comments

Comments
 (0)