Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fddd1b6
#40235 - Conditionally process collectTotal only if the value of requ…
Nov 14, 2025
b352228
#40235 - Adding the missed Discount
Nov 14, 2025
ae4d79f
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 14, 2025
8e7681a
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 14, 2025
a15df22
#40235 - Fixed Test Failures & added unit test
Nov 16, 2025
f4f87f8
#40235 - Removed Bundle product dependency
Nov 16, 2025
1d3f9ef
#40235 - Fixed Static Test Failures
Nov 16, 2025
0b047b9
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 17, 2025
099bb97
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 18, 2025
b216034
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 18, 2025
18095f9
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 20, 2025
86092fa
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 20, 2025
50e9916
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 21, 2025
ae280b8
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 25, 2025
ad6f4d6
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 26, 2025
b7d0fcc
Merge branch '2.4-develop' into FixFor-40235
senthilengg Nov 29, 2025
3757e5a
Merge branch '2.4-develop' into FixFor-40235
senthilengg Dec 1, 2025
988ae59
Merge branch '2.4-develop' into FixFor-40235
senthilengg Dec 2, 2025
45a4894
Merge branch '2.4-develop' into FixFor-40235
engcom-Hotel Dec 10, 2025
459592d
#40235 - Addressing review comments
Dec 11, 2025
4fbf031
Merge branch '2.4-develop' into FixFor-40235
senthilengg Dec 11, 2025
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
28 changes: 21 additions & 7 deletions app/code/Magento/QuoteGraphQl/Model/Resolver/CartItemPrices.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class CartItemPrices implements ResolverInterface, ResetAfterRequestInterface
*/
private $totals;

/**
* Fields that require totals to be collected.
* @var array
*/
private const FIELDS_REQUIRE_TOTALS_COLLECTION = ['discounts', 'original_item_price',
'original_row_total', 'catalog_discount', 'row_catalog_discount'];

/**
* CartItemPrices constructor.
*
Expand Down Expand Up @@ -64,24 +71,31 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value
}
/** @var Item $cartItem */
$cartItem = $value['model'];
if (!$this->totals) {

// Collect totals only if the fields in the request match any one of FIELDS_REQUIRE_TOTALS_COLLECTION array,
// except discounts the rest can be removed if the field values saved in db
if (!$this->totals && !empty(array_intersect(
self::FIELDS_REQUIRE_TOTALS_COLLECTION,
array_keys($info->getFieldSelection(1))
))
) {
// The totals calculation is based on quote address.
// But the totals should be calculated even if no address is set
$this->totals = $this->totalsCollector->collectQuoteTotals($cartItem->getQuote());
}

$currencyCode = $cartItem->getQuote()->getQuoteCurrencyCode();

/** calculate bundle product discount */
$discountAmount = 0;
if ($cartItem->getProductType() == 'bundle') {
$discounts = $cartItem->getExtensionAttributes()->getDiscounts() ?? [];
$discountAmount = 0;
foreach ($discounts as $discount) {
$discountAmount += $discount->getDiscountData()->getAmount();
foreach ($cartItem->getChildren() as $childItem) {
$discountAmount += $childItem->getDiscountAmount();
}
} else {
$discountAmount = $cartItem->getDiscountAmount();
}

$discountAmount += $cartItem->getDiscountAmount();

return [
'model' => $cartItem,
'price' => [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<?php
/**
* Copyright 2024 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\QuoteGraphQl\Test\Unit\Model\Resolver;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\GraphQl\Model\Query\Context;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Item;
use Magento\QuoteGraphQl\Model\Cart\TotalsCollector;
use Magento\QuoteGraphQl\Model\Resolver\CartItemPrices;
use Magento\QuoteGraphQl\Model\GetDiscounts;
use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\QuoteGraphQl\Model\GetOptionsRegularPrice;
use Magento\Framework\Api\ExtensionAttributesInterface;
use Magento\Catalog\Model\Product;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;

/**
* @see CartItemPrices
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CartItemPricesTest extends TestCase
{
/**
* @var CartItemPrices
*/
private CartItemPrices $cartItemPrices;

/**
* @var TotalsCollector|MockObject
*/
private TotalsCollector $totalsCollectorMock;

/**
* @var GetDiscounts |MockObject
*/
private GetDiscounts $getDiscountsMock;

/**
* @var PriceCurrencyInterface |MockObject
*/
private PriceCurrencyInterface $priceCurrencyMock;

/**
* @var GetOptionsRegularPrice |MockObject
*/
private GetOptionsRegularPrice $getOptionsRegularPriceMock;

/**
* @var Field|MockObject
*/
private Field $fieldMock;

/**
* @var ResolveInfo|MockObject
*/
private ResolveInfo $resolveInfoMock;

/**
* @var Context|MockObject
*/
private Context $contextMock;

/**
* @var Quote|MockObject
*/
private Quote $quoteMock;

/**
* @var Item|MockObject
*/
private Item $itemMock;

/**
* @var Product|MockObject
*/
private Product $productMock;

/**
* @var ExtensionAttributesInterface|MockObject
*/
private ExtensionAttributesInterface $itemExtensionMock;

/**
* @var array
*/
private array $valueMock = [];

protected function setUp(): void
{
$this->totalsCollectorMock = $this->createMock(TotalsCollector::class);
$this->getDiscountsMock = $this->createMock(GetDiscounts::class);
$this->priceCurrencyMock = $this->createMock(PriceCurrencyInterface::class);
$this->getOptionsRegularPriceMock = $this->createMock(GetOptionsRegularPrice::class);
$this->productMock = $this->getMockBuilder(Product::class)
->disableOriginalConstructor()
->onlyMethods(['getCustomOption'])
->getMock();
$this->fieldMock = $this->createMock(Field::class);
$this->resolveInfoMock = $this->createMock(ResolveInfo::class);
$this->contextMock = $this->createMock(Context::class);
$this->quoteMock = $this->getMockBuilder(Quote::class)
->disableOriginalConstructor()
->addMethods(['getQuoteCurrencyCode'])
->getMock();
$this->itemMock = $this->getMockBuilder(Item::class)
->addMethods([
'getPriceInclTax', 'getRowTotal',
'getRowTotalInclTax', 'getDiscountAmount'
])
->onlyMethods([
'getCalculationPrice', 'getQuote', 'getExtensionAttributes',
'getProduct', 'getOriginalPrice'
])
->disableOriginalConstructor()
->getMock();
$this->itemExtensionMock = $this->getMockBuilder(
ExtensionAttributesInterface::class
)->addMethods(['getDiscounts'])->getMockForAbstractClass();

$this->cartItemPrices = new CartItemPrices(
$this->totalsCollectorMock,
$this->getDiscountsMock,
$this->priceCurrencyMock,
$this->getOptionsRegularPriceMock
);
}

public function testResolve(): void
{
$this->valueMock = ['model' => $this->itemMock];

$this->resolveInfoMock->expects($this->once())
->method('getFieldSelection')
->with(1)
->willReturn([]);

$this->itemMock
->expects($this->exactly(2))
->method('getQuote')
->willReturn($this->quoteMock);

$this->quoteMock
->expects($this->once())
->method('getQuoteCurrencyCode')
->willReturn('USD');

$this->itemMock
->expects($this->once())
->method('getDiscountAmount');

$this->itemMock
->expects($this->once())
->method('getCalculationPrice');

$this->itemMock
->expects($this->once())
->method('getPriceInclTax');

$this->itemMock
->expects($this->any())
->method('getOriginalPrice')
->willReturn(0);

$this->itemMock
->expects($this->once())
->method('getRowTotal');

$this->itemMock
->expects($this->once())
->method('getRowTotalInclTax');

$this->itemMock
->expects($this->once())
->method('getExtensionAttributes')
->willReturn($this->itemExtensionMock);

$this->itemMock
->expects($this->any())
->method('getProduct')
->willReturn($this->productMock);

$this->productMock
->expects($this->exactly(2))
->method('getCustomOption')
->willReturn(null);

$this->itemExtensionMock
->expects($this->once())
->method('getDiscounts')
->willReturn([]);

$this->getDiscountsMock
->expects($this->once())
->method('execute')
->with($this->quoteMock, []);

$this->cartItemPrices->resolve($this->fieldMock, $this->contextMock, $this->resolveInfoMock, $this->valueMock);
}

public function testResolveWithoutModelInValueParameter(): void
{
$this->expectException(LocalizedException::class);
$this->expectExceptionMessage('"model" value should be specified');
$this->cartItemPrices->resolve($this->fieldMock, $this->contextMock, $this->resolveInfoMock, $this->valueMock);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
use Magento\Quote\Model\QuoteFactory;

/**
* Get masked quote id by reserved order id
*/
class GetMaskedQuoteIdByReservedOrderId
{
/**
Expand Down Expand Up @@ -51,15 +48,21 @@ public function __construct(
* Get masked quote id by reserved order id
*
* @param string $reservedOrderId
* @param bool $shouldCollectTotals
* @return string
* @throws NoSuchEntityException
*/
public function execute(string $reservedOrderId): string
public function execute(string $reservedOrderId, bool $shouldCollectTotals = false): string
{
$quote = $this->quoteFactory->create();
$quote->setSharedStoreIds(['*']);
$this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id');

// If dataprovider is used, we need to collect totals manually and save quote
if ($shouldCollectTotals) {
$this->quoteResource->save($quote->collectTotals());
}

return $this->quoteIdToMaskedId->execute((int)$quote->getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ private function writeConfig(array $settings): void
public function testCartItemFixedProductTax(array $taxSettings, array $expectedFtps): void
{
$this->writeConfig($taxSettings);
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
// Second argument true is to ensure collectTotals and save after every quote update
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote', true);
$query = $this->getQuery($maskedQuoteId);
$result = $this->graphQlQuery($query);
$this->assertArrayNotHasKey('errors', $result);
Expand Down