Skip to content

Commit

Permalink
Merge branch '2.4-develop' into 32116
Browse files Browse the repository at this point in the history
  • Loading branch information
engcom-Charlie committed Feb 18, 2021
2 parents d594fa7 + 840b5f8 commit 0ecec5d
Show file tree
Hide file tree
Showing 110 changed files with 4,754 additions and 650 deletions.
109 changes: 109 additions & 0 deletions app/code/Magento/Bundle/Model/Quote/Item/Option.php
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())
: [];
}
}
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;
}
}
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]);
}
}
}
}
}
}
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>
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>
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>
Loading

0 comments on commit 0ecec5d

Please sign in to comment.