Skip to content

Commit a44cbab

Browse files
ENGCOM-7141: Fixes #12584 Bundle Item price cannot differ per website #27315
2 parents 73a749e + 15015fa commit a44cbab

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)