-
Notifications
You must be signed in to change notification settings - Fork 9.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '2.4-develop' into 32116
- Loading branch information
Showing
110 changed files
with
4,754 additions
and
650 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\Bundle\Model\Quote\Item; | ||
|
||
use Magento\Bundle\Model\Product\Price; | ||
use Magento\Bundle\Model\Product\Type; | ||
use Magento\Catalog\Model\Product; | ||
use Magento\Framework\Serialize\Serializer\Json; | ||
|
||
/** | ||
* Bundle product options model | ||
*/ | ||
class Option | ||
{ | ||
/** | ||
* @var Json | ||
*/ | ||
private $serializer; | ||
|
||
/** | ||
* @param Json $serializer | ||
*/ | ||
public function __construct( | ||
Json $serializer | ||
) { | ||
$this->serializer = $serializer; | ||
} | ||
|
||
/** | ||
* Get selection options for provided bundle product | ||
* | ||
* @param Product $product | ||
* @return array | ||
*/ | ||
public function getSelectionOptions(Product $product): array | ||
{ | ||
$options = []; | ||
$bundleOptionIds = $this->getOptionValueAsArray($product, 'bundle_option_ids'); | ||
if ($bundleOptionIds) { | ||
/** @var Type $typeInstance */ | ||
$typeInstance = $product->getTypeInstance(); | ||
$optionsCollection = $typeInstance->getOptionsByIds($bundleOptionIds, $product); | ||
$selectionIds = $this->getOptionValueAsArray($product, 'bundle_selection_ids'); | ||
|
||
if ($selectionIds) { | ||
$selectionsCollection = $typeInstance->getSelectionsByIds($selectionIds, $product); | ||
$optionsCollection->appendSelections($selectionsCollection, true); | ||
|
||
foreach ($selectionsCollection as $selection) { | ||
$selectionId = $selection->getSelectionId(); | ||
$options[$selectionId][] = $this->getBundleSelectionAttributes($product, $selection); | ||
} | ||
} | ||
} | ||
|
||
return $options; | ||
} | ||
|
||
/** | ||
* Get selection attributes for provided selection | ||
* | ||
* @param Product $product | ||
* @param Product $selection | ||
* @return array | ||
*/ | ||
private function getBundleSelectionAttributes(Product $product, Product $selection): array | ||
{ | ||
$selectionId = $selection->getSelectionId(); | ||
/** @var \Magento\Bundle\Model\Option $bundleOption */ | ||
$bundleOption = $selection->getOption(); | ||
/** @var Price $priceModel */ | ||
$priceModel = $product->getPriceModel(); | ||
$price = $priceModel->getSelectionFinalTotalPrice($product, $selection, 0, 1); | ||
$customOption = $product->getCustomOption('selection_qty_' . $selectionId); | ||
$qty = (float)($customOption ? $customOption->getValue() : 0); | ||
|
||
return [ | ||
'code' => 'bundle_selection_attributes', | ||
'value'=> $this->serializer->serialize( | ||
[ | ||
'price' => $price, | ||
'qty' => $qty, | ||
'option_label' => $bundleOption->getTitle(), | ||
'option_id' => $bundleOption->getId(), | ||
] | ||
) | ||
]; | ||
} | ||
|
||
/** | ||
* Get unserialized value of custom option | ||
* | ||
* @param Product $product | ||
* @param string $code | ||
* @return array | ||
*/ | ||
private function getOptionValueAsArray(Product $product, string $code): array | ||
{ | ||
$option = $product->getCustomOption($code); | ||
return $option && $option->getValue() | ||
? $this->serializer->unserialize($option->getValue()) | ||
: []; | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
app/code/Magento/Bundle/Model/Quote/Item/Option/BundleSelectionAttributesComparator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\Bundle\Model\Quote\Item\Option; | ||
|
||
use Magento\Framework\DataObject; | ||
use Magento\Framework\Serialize\Serializer\Json; | ||
use Magento\Quote\Model\Quote\Item\Option\ComparatorInterface; | ||
|
||
/** | ||
* Bundle quote item option comparator | ||
*/ | ||
class BundleSelectionAttributesComparator implements ComparatorInterface | ||
{ | ||
/** | ||
* @var Json | ||
*/ | ||
private $serializer; | ||
|
||
/** | ||
* @param Json $serializer | ||
*/ | ||
public function __construct( | ||
Json $serializer | ||
) { | ||
$this->serializer = $serializer; | ||
} | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function compare(DataObject $option1, DataObject $option2): bool | ||
{ | ||
$value1 = $option1->getValue() ? $this->serializer->unserialize($option1->getValue()) : []; | ||
$value2 = $option2->getValue() ? $this->serializer->unserialize($option2->getValue()) : []; | ||
$option1Id = isset($value1['option_id']) ? (int) $value1['option_id'] : null; | ||
$option2Id = isset($value2['option_id']) ? (int) $value2['option_id'] : null; | ||
|
||
return $option1Id === $option2Id; | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
app/code/Magento/Bundle/Plugin/Quote/UpdateBundleQuoteItemOptions.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
declare(strict_types=1); | ||
|
||
namespace Magento\Bundle\Plugin\Quote; | ||
|
||
use Magento\Bundle\Model\Product\Type; | ||
use Magento\Bundle\Model\Quote\Item\Option; | ||
use Magento\Quote\Model\Quote; | ||
use Magento\Quote\Model\Quote\Item; | ||
use Magento\Quote\Model\QuoteManagement; | ||
|
||
/** | ||
* Update bundle selection custom options | ||
*/ | ||
class UpdateBundleQuoteItemOptions | ||
{ | ||
/** | ||
* @var Option | ||
*/ | ||
private $option; | ||
|
||
/** | ||
* @param Option $option | ||
*/ | ||
public function __construct( | ||
Option $option | ||
) { | ||
$this->option = $option; | ||
} | ||
|
||
/** | ||
* Update bundle selection custom options before order is placed | ||
* | ||
* @param QuoteManagement $subject | ||
* @param Quote $quote | ||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||
*/ | ||
public function beforeSubmit( | ||
QuoteManagement $subject, | ||
Quote $quote, | ||
array $orderData = [] | ||
): void { | ||
foreach ($quote->getAllVisibleItems() as $quoteItem) { | ||
if ($quoteItem->getProductType() === Type::TYPE_CODE) { | ||
$options = $this->option->getSelectionOptions($quoteItem->getProduct()); | ||
foreach ($quoteItem->getChildren() as $childItem) { | ||
/** @var Item $childItem */ | ||
$customOption = $childItem->getOptionByCode('selection_id'); | ||
$selectionId = $customOption ? $customOption->getValue() : null; | ||
if ($selectionId && isset($options[$selectionId])) { | ||
$childItem->setOptions($options[$selectionId]); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
app/code/Magento/Bundle/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
--> | ||
|
||
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> | ||
<section name="AdminOrderItemsOrderedSection"> | ||
<element name="orderItemOptionLabel" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-product .option-label" parameterized="true"/> | ||
<element name="orderItemOptionValue" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-product .option-value" parameterized="true"/> | ||
<element name="orderItemOptionPrice" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-product .option-value .price" parameterized="true"/> | ||
</section> | ||
</sections> |
15 changes: 15 additions & 0 deletions
15
app/code/Magento/Bundle/Test/Mftf/Section/StorefrontCustomerOrderSection.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
--> | ||
|
||
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> | ||
<section name="StorefrontCustomerOrderSection"> | ||
<element name="orderItemOptionLabel" type="text" selector=".table-order-items tr:nth-of-type({{row}}) td.label" parameterized="true"/> | ||
<element name="orderItemOptionValue" type="text" selector=".table-order-items tr:nth-of-type({{row}}) td.value" parameterized="true"/> | ||
</section> | ||
</sections> |
141 changes: 141 additions & 0 deletions
141
...Bundle/Test/Mftf/Test/StorefrontPlaceOrderBundleProductFixedPriceWithUpdatedPriceTest.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
<?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="StorefrontPlaceOrderBundleProductFixedPriceWithUpdatedPriceTest"> | ||
<annotations> | ||
<features value="Bundle"/> | ||
<stories value="Placing order with bundle product"/> | ||
<title value="Order details with bundle product fixed price should show the correct price for bundle items"/> | ||
<description value="Order details with bundle product fixed price should show the correct price for bundle items"/> | ||
<severity value="MAJOR"/> | ||
<useCaseId value="MC-40603"/> | ||
<testCaseId value="MC-40744"/> | ||
<group value="bundle"/> | ||
<group value="catalog"/> | ||
</annotations> | ||
|
||
<before> | ||
<createData entity="CustomerEntityOne" stepKey="createCustomer"/> | ||
<createData entity="SimpleProduct2" stepKey="createFirstProduct"/> | ||
<createData entity="SimpleProduct2" stepKey="createSecondProduct"/> | ||
<createData entity="ApiFixedBundleProduct" stepKey="createFixedBundleProduct"> | ||
<field key="price">11.00</field> | ||
</createData> | ||
<createData entity="RadioButtonsOption" stepKey="createFirstBundleOption"> | ||
<field key="position">1</field> | ||
<requiredEntity createDataKey="createFixedBundleProduct"/> | ||
</createData> | ||
<createData entity="RadioButtonsOption" stepKey="createSecondBundleOption"> | ||
<field key="position">2</field> | ||
<requiredEntity createDataKey="createFixedBundleProduct"/> | ||
</createData> | ||
<createData entity="ApiBundleLink" stepKey="firstLinkOptionToFixedProduct"> | ||
<requiredEntity createDataKey="createFixedBundleProduct"/> | ||
<requiredEntity createDataKey="createFirstBundleOption"/> | ||
<requiredEntity createDataKey="createFirstProduct"/> | ||
<field key="price_type">0</field> | ||
<field key="price">7.00</field> | ||
</createData> | ||
<createData entity="ApiBundleLink" stepKey="secondLinkOptionToFixedProduct"> | ||
<requiredEntity createDataKey="createFixedBundleProduct"/> | ||
<requiredEntity createDataKey="createSecondBundleOption"/> | ||
<requiredEntity createDataKey="createSecondProduct"/> | ||
<field key="price_type">0</field> | ||
<field key="price">5.00</field> | ||
</createData> | ||
<actionGroup stepKey="loginToAdminPanel" ref="AdminLoginActionGroup"/> | ||
<actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="goToProductEditPage"> | ||
<argument name="productId" value="$createFixedBundleProduct.id$"/> | ||
</actionGroup> | ||
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> | ||
</before> | ||
<after> | ||
<deleteData createDataKey="createFirstProduct" stepKey="deleteSimpleProductForBundleItem"/> | ||
<deleteData createDataKey="createSecondProduct" stepKey="deleteVirtualProductForBundleItem"/> | ||
<deleteData createDataKey="createFixedBundleProduct" stepKey="deleteBundleProduct"/> | ||
<deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> | ||
<actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductsGridFilters"/> | ||
<waitForPageLoad stepKey="waitForClearProductsGridFilters"/> | ||
</after> | ||
<!--Login customer on storefront--> | ||
<actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginCustomer"> | ||
<argument name="Customer" value="$$createCustomer$$" /> | ||
</actionGroup> | ||
<!--Open Product Page--> | ||
<actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openBundleProductPage"> | ||
<argument name="product" value="$createFixedBundleProduct$"/> | ||
</actionGroup> | ||
<!--Add bundle to cart--> | ||
<actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickAddToCart"/> | ||
<actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> | ||
<argument name="quantity" value="1"/> | ||
</actionGroup> | ||
<!--Open bundle product in admin--> | ||
<actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="goToProductEditPage"> | ||
<argument name="productId" value="$createFixedBundleProduct.id$"/> | ||
</actionGroup> | ||
<!--Change price of the first option--> | ||
<fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYPrice('0', '0')}}" userInput="9" stepKey="fillBundleOption1Price"/> | ||
<!--Save the bundle product--> | ||
<actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> | ||
<!--Open Product Page--> | ||
<actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openBundleProductPage2"> | ||
<argument name="product" value="$createFixedBundleProduct$"/> | ||
</actionGroup> | ||
<!--Add bundle to cart--> | ||
<actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickAddToCart2"/> | ||
<actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart2"> | ||
<argument name="quantity" value="1"/> | ||
</actionGroup> | ||
<!--Verify bundle product details--> | ||
<actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> | ||
<see selector="{{StorefrontBundledSection.nthItemOptionsTitle('1')}}" userInput="$$createFirstBundleOption.title$$" stepKey="seeOptionLabelInShoppingCart"/> | ||
<see selector="{{StorefrontBundledSection.nthItemOptionsValue('1')}}" userInput="1 x $$createFirstProduct.name$$ $9.00" stepKey="seeOptionValueInShoppingCart"/> | ||
<see selector="{{StorefrontBundledSection.nthItemOptionsTitle('2')}}" userInput="$$createSecondBundleOption.title$$" stepKey="seeOption2LabelInShoppingCart"/> | ||
<see selector="{{StorefrontBundledSection.nthItemOptionsValue('2')}}" userInput="1 x $$createSecondProduct.name$$ $5.00" stepKey="seeOption2ValueInShoppingCart"/> | ||
<!--Verify total--> | ||
<grabTextFrom selector="{{CheckoutCartSummarySection.total}}" stepKey="grabShoppingCartTotal"/> | ||
<assertEquals stepKey="verifyGrandTotalOnShoppingCartPage"> | ||
<actualResult type="variable">grabShoppingCartTotal</actualResult> | ||
<expectedResult type="string">$60.00</expectedResult> | ||
</assertEquals> | ||
|
||
<!--Navigate to checkout--> | ||
<actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="openCheckoutPage"/> | ||
<!--Click next button to open payment section--> | ||
<actionGroup ref="StorefrontCheckoutClickNextButtonActionGroup" stepKey="clickNext"/> | ||
<!--Click place order--> | ||
<actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> | ||
<grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> | ||
<!--Navigate to order details page in custom account--> | ||
<amOnPage url="{{StorefrontCustomerOrderViewPage.url({$grabOrderNumber})}}" stepKey="amOnOrderPage"/> | ||
<!--Verify bundle order items details--> | ||
<see selector="{{StorefrontCustomerOrderSection.orderItemOptionLabel('2')}}" userInput="$$createFirstBundleOption.title$$" stepKey="seeOptionLabelInCustomerOrderItems"/> | ||
<see selector="{{StorefrontCustomerOrderSection.orderItemOptionValue('3')}}" userInput="1 x $$createFirstProduct.name$$ $9.00" stepKey="seeOptionValueInCustomerOrderItems"/> | ||
<see selector="{{StorefrontCustomerOrderSection.orderItemOptionLabel('4')}}" userInput="$$createSecondBundleOption.title$$" stepKey="seeOption2LabelInCustomerOrderItems"/> | ||
<see selector="{{StorefrontCustomerOrderSection.orderItemOptionValue('5')}}" userInput="1 x $$createSecondProduct.name$$ $5.00" stepKey="seeOption2ValueInCustomerOrderItems"/> | ||
<!--Navigate to order details page on admin--> | ||
<actionGroup ref="OpenOrderByIdActionGroup" stepKey="filterOrdersGridById"> | ||
<argument name="orderId" value="{$grabOrderNumber}"/> | ||
</actionGroup> | ||
<!--Verify bundle order items details--> | ||
<see selector="{{AdminOrderItemsOrderedSection.orderItemOptionLabel('2')}}" userInput="$$createFirstBundleOption.title$$" stepKey="seeOptionLabelInAdminOrderItems"/> | ||
<see selector="{{AdminOrderItemsOrderedSection.orderItemOptionValue('3')}}" userInput="1 x $$createFirstProduct.name$$" stepKey="seeOptionValueInAdminOrderItems"/> | ||
<see selector="{{AdminOrderItemsOrderedSection.orderItemOptionPrice('3')}}" userInput="$9.00" stepKey="seeOptionPriceInAdminOrderItems"/> | ||
<see selector="{{AdminOrderItemsOrderedSection.orderItemOptionLabel('4')}}" userInput="$$createSecondBundleOption.title$$" stepKey="seeOption2LabelInAdminOrderItems"/> | ||
<see selector="{{AdminOrderItemsOrderedSection.orderItemOptionValue('5')}}" userInput="1 x $$createSecondProduct.name$$" stepKey="seeOption2ValueInAdminOrderItems"/> | ||
<see selector="{{AdminOrderItemsOrderedSection.orderItemOptionPrice('5')}}" userInput="$5.00" stepKey="seeOption2PriceInAdminOrderItems"/> | ||
<!--Verify total--> | ||
<grabTextFrom selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="grabAdminOrderTotal"/> | ||
<assertEquals stepKey="verifyGrandTotalOnAdminOrderPage"> | ||
<actualResult type="variable">grabAdminOrderTotal</actualResult> | ||
<expectedResult type="string">$60.00</expectedResult> | ||
</assertEquals> | ||
</test> | ||
</tests> |
Oops, something went wrong.