Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #12584 Bundle Item price cannot differ per website #27315

Merged
merged 31 commits into from
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b25a34f
WIP: Fix for issue 12584 - save scoped bundle prices
rain2o Mar 10, 2020
fc9559e
Merge branch '2.4-develop' into bugfix/issue-12584
rain2o Mar 16, 2020
a59b0e8
Set storemanager prop.
rain2o Mar 17, 2020
8cca552
Add missing doc block for beforeSave.
rain2o Mar 17, 2020
49c261e
Fixed typo in docblock
rain2o Mar 18, 2020
659beb2
Fixes for failed static tests in Bundle module.
rain2o Mar 18, 2020
f75a78b
Merge branch '2.4-develop' of github.com:magento/magento2 into bugfix…
rain2o Jul 10, 2020
fd40a7b
Avoid duplicate keys when updating from select
rain2o Jul 10, 2020
d7f283c
Merge branch '2.4-develop' into bugfix/issue-12584
rain2o Aug 21, 2020
56ef44b
Cleanup changes from PR #26397
rain2o Aug 21, 2020
b5c9757
Merge branch '2.4-develop' into bugfix/issue-12584
engcom-Echo Sep 16, 2020
9f622ee
fix unit, static tests
engcom-Echo Sep 16, 2020
64db19a
refactoring SaveAction.php, add MFTF test
engcom-Echo Sep 16, 2020
dc7cc2c
Merge branch '2.4-develop' into bugfix/issue-12584
Sep 17, 2020
ed0f987
minor fix. added required annotations
engcom-Echo Sep 17, 2020
ac37ae1
magento/magento2#12584: Bundle Item price cannot differ per website.
engcom-Foxtrot Nov 19, 2020
975c0c1
magento/magento2#12584: Bundle Item price cannot differ per website.
engcom-Foxtrot Nov 19, 2020
171c54d
Merge branch '2.4-develop' into bugfix/issue-12584
Nov 24, 2020
a2a9cba
Merge branch '2.4-develop' into bugfix/issue-12584
Jan 5, 2021
ea54519
Merge branch '2.4-develop' into bugfix/issue-12584
Jan 5, 2021
377e070
Merge branch '2.4-develop' into bugfix/issue-12584
Jan 11, 2021
605fb88
magento/magento2#29549: Scheduled price rule time zone correction - r…
engcom-Foxtrot Jan 12, 2021
6cc13cc
Merge remote-tracking branch 'rain2o/bugfix/issue-12584' into bugfix/…
engcom-Foxtrot Jan 12, 2021
88ea963
magento/magento2#12584 Bundle Item price cannot differ per website - …
engcom-Foxtrot Jan 14, 2021
dc3226b
Merge branch '2.4-develop' into bugfix/issue-12584
Jan 22, 2021
7da12c1
Merge branch '2.4-develop' into bugfix/issue-12584
Jan 25, 2021
c57d4d0
Removed unnecessary parameter
Jan 27, 2021
f6bb216
Merge branch '2.4-develop' into bugfix/issue-12584
Jan 27, 2021
27627ca
Fixed unit tests
Jan 28, 2021
b00b423
Merge branch '2.4-develop' into bugfix/issue-12584
Jan 28, 2021
eb5e30c
Merge branch 'bugfix/issue-12584' of github.com:rain2o/magento2 into …
Jan 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/code/Magento/Bundle/Model/LinkManagement.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ protected function mapProductLinkToSelectionModel(
if ($productLink->getIsDefault() !== null) {
$selectionModel->setIsDefault($productLink->getIsDefault());
}
if ($productLink->getWebsiteId() !== null) {
$selectionModel->setWebsiteId($productLink->getWebsiteId());
}

return $selectionModel;
}
Expand Down
31 changes: 22 additions & 9 deletions app/code/Magento/Bundle/Model/Option/SaveAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@

namespace Magento\Bundle\Model\Option;

use Magento\Bundle\Api\Data\LinkInterface;
use Magento\Bundle\Api\Data\OptionInterface;
use Magento\Bundle\Model\ResourceModel\Option;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Bundle\Model\Product\Type;
use Magento\Bundle\Api\ProductLinkManagementInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
* Encapsulates logic for saving a bundle option, including coalescing the parent product's data.
Expand All @@ -40,22 +43,31 @@ class SaveAction
*/
private $linkManagement;

/**
* @var StoreManagerInterface
*/
private $storeManager;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently we are not using the storeManager in this class, can we remove it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/**
* @param Option $optionResource
* @param MetadataPool $metadataPool
* @param Type $type
* @param ProductLinkManagementInterface $linkManagement
* @param StoreManagerInterface|null $storeManager
*/
public function __construct(
Option $optionResource,
MetadataPool $metadataPool,
Type $type,
ProductLinkManagementInterface $linkManagement
ProductLinkManagementInterface $linkManagement,
?StoreManagerInterface $storeManager = null
) {
$this->optionResource = $optionResource;
$this->metadataPool = $metadataPool;
$this->type = $type;
$this->linkManagement = $linkManagement;
$this->storeManager = $storeManager
?: ObjectManager::getInstance()->get(StoreManagerInterface::class);
}

/**
Expand All @@ -69,7 +81,7 @@ public function __construct(
*/
public function save(ProductInterface $bundleProduct, OptionInterface $option)
{
$metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);

$option->setStoreId($bundleProduct->getStoreId());
$parentId = $bundleProduct->getData($metadata->getLinkField());
Expand All @@ -93,7 +105,7 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option)
}
} else {
if (!$existingOption->getOptionId()) {
throw new NoSuchEntityException(
throw new \Magento\Framework\Exception\NoSuchEntityException(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we import this instead of having the full path? Just to align with other changes on the PR.

__("The option that was requested doesn't exist. Verify the entity and try again.")
);
}
Expand All @@ -108,7 +120,7 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option)
throw new CouldNotSaveException(__("The option couldn't be saved."), $e);
}

/** @var \Magento\Bundle\Api\Data\LinkInterface $linkedProduct */
/** @var LinkInterface $linkedProduct */
foreach ($linksToAdd as $linkedProduct) {
$this->linkManagement->addChild($bundleProduct, $option->getOptionId(), $linkedProduct);
}
Expand All @@ -121,8 +133,8 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option)
/**
* Update option selections
*
* @param \Magento\Catalog\Api\Data\ProductInterface $product
* @param \Magento\Bundle\Api\Data\OptionInterface $option
* @param ProductInterface $product
* @param OptionInterface $option
* @return void
*/
private function updateOptionSelection(ProductInterface $product, OptionInterface $option)
Expand All @@ -135,13 +147,14 @@ private function updateOptionSelection(ProductInterface $product, OptionInterfac
if (is_array($option->getProductLinks())) {
$productLinks = $option->getProductLinks();
foreach ($productLinks as $productLink) {
$productLink->setWebsiteId($this->storeManager->getStore($product->getStoreId())->getWebsiteId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we add such set on the save method on this class as well? Or is it just relevant when we update an option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gabrieldagama I believe it is only relevant when updating an option, unless I am missing something. The only time this isn't called from save I believe is when creating the product options, and I think you need to create them at the root level before editing any store/website scopes, correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method $option->getProductLinks(); return LinkInterface so we will need to have the setWebsiteId and getWebsiteId to the interface and DTO. Be aware that we need to implement it on a backwards compatible way.

if (!$productLink->getId() && !$productLink->getSelectionId()) {
$linksToAdd[] = $productLink;
} else {
$linksToUpdate[] = $productLink;
}
}
/** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToDelete */
/** @var LinkInterface[] $linksToDelete */
$linksToDelete = $this->compareLinks($existingLinks, $linksToUpdate);
}
foreach ($linksToUpdate as $linkedProduct) {
Expand All @@ -162,8 +175,8 @@ private function updateOptionSelection(ProductInterface $product, OptionInterfac
/**
* Compute the difference between given arrays.
*
* @param \Magento\Bundle\Api\Data\LinkInterface[] $firstArray
* @param \Magento\Bundle\Api\Data\LinkInterface[] $secondArray
* @param LinkInterface[] $firstArray
* @param LinkInterface[] $secondArray
*
* @return array
*/
Expand Down
38 changes: 37 additions & 1 deletion app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,42 @@ private function getBaseBundleSelectionPriceSelect(): Select
return $select;
}

/**
* Get base select for bundle selection price update
*
* @return Select
* @throws \Exception
*/
private function getBaseBundleSelectionPriceUpdateSelect(): Select
gabrieldagama marked this conversation as resolved.
Show resolved Hide resolved
{
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
$linkField = $metadata->getLinkField();
$bundleSelectionTable = $this->getBundleSelectionTable();

$select = $this->getConnection()->select()
->join(
['i' => $this->getBundlePriceTable()],
"i.entity_id = $bundleSelectionTable.entity_id
AND i.customer_group_id = $bundleSelectionTable.customer_group_id
AND i.website_id = $bundleSelectionTable.website_id",
[]
)->join(
['parent_product' => $this->getTable('catalog_product_entity')],
'parent_product.entity_id = i.entity_id',
[]
)->join(
['bo' => $this->getTable('catalog_product_bundle_option')],
"bo.parent_id = parent_product.$linkField AND bo.option_id = $bundleSelectionTable.option_id",
['option_id']
)->join(
['bs' => $this->getTable('catalog_product_bundle_selection')],
"bs.option_id = bo.option_id AND bs.selection_id = $bundleSelectionTable.selection_id",
['selection_id']
);

return $select;
}

/**
* Apply selections price for fixed bundles
*
Expand Down Expand Up @@ -498,7 +534,7 @@ private function applyFixedBundleSelectionPrice()
]
);

$select = $this->getBaseBundleSelectionPriceSelect();
$select = $this->getBaseBundleSelectionPriceUpdateSelect();
$select->joinInner(
['bsp' => $this->getTable('catalog_product_bundle_selection_price')],
'bs.selection_id = bsp.selection_id AND bsp.website_id = i.website_id',
Expand Down
21 changes: 20 additions & 1 deletion app/code/Magento/Bundle/Model/Selection.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
* @method \Magento\Bundle\Model\Selection setPosition(int $value)
* @method int getIsDefault()
* @method \Magento\Bundle\Model\Selection setIsDefault(int $value)
* @method int getWebsiteId()
* @method \Magento\Bundle\Model\Selection setWebsiteId(int $value)
Comment on lines +23 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we aren't using this approach anymore, this can probably be removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods are still used in another classes.

* @method int getSelectionPriceType()
* @method \Magento\Bundle\Model\Selection setSelectionPriceType(int $value)
* @method float getSelectionPriceValue()
Expand Down Expand Up @@ -74,18 +76,35 @@ protected function _construct()
/**
* Processing object before save data
*
* @return void
*/
public function beforeSave()
{
if (!$this->_catalogData->isPriceGlobal() && $this->getWebsiteId()) {
$this->setData('tmp_selection_price_value', $this->getSelectionPriceValue());
$this->setSelectionPriceValue($this->getOrigData('selection_price_value'));
}
parent::beforeSave();
}

/**
* Processing object after save data
*
* @return $this
*/
public function afterSave()
{
if (!$this->_catalogData->isPriceGlobal() && $this->getWebsiteId()) {
if (null !== $this->getData('tmp_selection_price_value')) {
$this->setSelectionPriceValue($this->getData('tmp_selection_price_value'));
}
$this->getResource()->saveSelectionPrice($this);

if (!$this->getDefaultPriceScope()) {
$this->unsSelectionPriceValue();
$this->unsSelectionPriceType();
}
}
parent::afterSave();
return parent::afterSave();
}
}
9 changes: 9 additions & 0 deletions app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@
<data key="price_type">1</data>
<data key="can_change_quantity">1</data>
</entity>
<entity name="ApiBundleLinkFixed" type="bundle_link">
<var key="option_id" entityKey="return" entityType="bundle_option"/>
<var key="sku" entityKey="sku" entityType="product"/>
<data key="qty">1</data>
<data key="is_default">1</data>
<data key="price">30</data>
<data key="price_type">0</data>
<data key="can_change_quantity">1</data>
</entity>
</entities>
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->

<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
<test name="StorefrontCheckBundleProductTwoWebsiteDifferentPriceOptionTest">
<annotations>
<title value="Verify bundle item price different websites."/>
<stories value="Github issue: #12584 Bundle Item price cannot differ per website"/>
<description value="Verify bundle item price different websites. Change bundle item price on second website."/>
<features value="Bundle"/>
<severity value="MAJOR"/>
<group value="bundle"/>
</annotations>
<before>
<magentoCLI command="config:set {{WebsiteCatalogPriceScopeConfigData.path}} {{WebsiteCatalogPriceScopeConfigData.value}}" stepKey="setPriceScopeWebsite"/>
<actionGroup ref="AdminLoginActionGroup" stepKey="logInAsAdmin"/>

<actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite">
<argument name="newWebsiteName" value="{{customWebsite.name}}"/>
<argument name="websiteCode" value="{{customWebsite.code}}"/>
</actionGroup>
<actionGroup ref="CreateCustomStoreActionGroup" stepKey="createCustomStoreGroup">
<argument name="website" value="{{customWebsite.name}}"/>
<argument name="store" value="{{customStoreGroup.name}}"/>
<argument name="rootCategory" value="Default Category"/>
</actionGroup>
<actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView">
<argument name="StoreGroup" value="customStoreGroup"/>
<argument name="customStore" value="customStore"/>
</actionGroup>

<createData entity="SimpleProduct2" stepKey="simpleProduct"/>
<createData entity="ApiFixedBundleProduct" stepKey="createBundleProduct" />
<createData entity="CheckboxOption" stepKey="createBundleOption">
<requiredEntity createDataKey="createBundleProduct"/>
</createData>
<createData entity="ApiBundleLinkFixed" stepKey="linkOptionToProduct">
<requiredEntity createDataKey="createBundleProduct"/>
<requiredEntity createDataKey="createBundleOption"/>
<requiredEntity createDataKey="simpleProduct"/>
</createData>
</before>
<after>
<magentoCLI command="config:set {{GlobalCatalogPriceScopeConfigData.path}} {{GlobalCatalogPriceScopeConfigData.value}}" stepKey="setPriceScopeGlobal"/>

<deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/>
<deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/>

<actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite">
<argument name="websiteName" value="{{customWebsite.name}}"/>
</actionGroup>
<actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/>

<actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex">
<argument name="indices" value=""/>
</actionGroup>
<actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanFullPageCache">
<argument name="tags" value="config full_page"/>
</actionGroup>
</after>

<actionGroup ref="NavigateToCreatedProductEditPageActionGroup" stepKey="openEditBundleProduct">
<argument name="product" value="$$createBundleProduct$$"/>
</actionGroup>

<actionGroup ref="AdminAssignProductInWebsiteActionGroup" stepKey="selectProductInWebsites">
<argument name="website" value="{{customWebsite.name}}"/>
</actionGroup>
<actionGroup ref="SaveProductFormActionGroup" stepKey="clickSaveButton"/>
<actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="SwitchNewStoreView">
<argument name="storeViewName" value="{{customStore.name}}"/>
</actionGroup>

<fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYPrice('0', '0')}}" userInput="100" stepKey="fillBundleOption1Price"/>

<actionGroup ref="SaveProductFormActionGroup" stepKey="saveNewPrice"/>
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage">
<argument name="productUrl" value="$$createBundleProduct.custom_attributes[url_key]$$"/>
</actionGroup>

<click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomizeAndAddToCart"/>

<grabTextFrom selector="{{StorefrontBundledSection.bundleProductsPrice}}" stepKey="grabPriceText"/>
<assertEquals stepKey="assertPriceText">
<expectedResult type="string">$31.23</expectedResult>
<actualResult type="variable">$grabPriceText</actualResult>
</assertEquals>

</test>
</tests>
Loading