Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 18 additions & 7 deletions app/code/Magento/QuoteGraphQl/Model/Resolver/CartItemPrices.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,35 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value
}
/** @var Item $cartItem */
$cartItem = $value['model'];
if (!$this->totals) {

// Collect totals only if discount, original item price and original rowtotal is there in the request
// avoid retrieve totals with the below keys if its not absolutely required
// except discounts can be removed if original_item_price and original_row_total is saved in db
if (!$this->totals && !empty(array_intersect(
[
'discounts', 'original_item_price', 'original_row_total',
'catalog_discount', 'row_catalog_discount'
],
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
@@ -1,7 +1,7 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
* Copyright 2024 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

Expand All @@ -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 $forceCollectTotal
* @return string
* @throws NoSuchEntityException
*/
public function execute(string $reservedOrderId): string
public function execute(string $reservedOrderId, bool $forceCollectTotal = 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 ($forceCollectTotal) {
$this->quoteResource->save($quote->collectTotals());
}

return $this->quoteIdToMaskedId->execute((int)$quote->getId());
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
* Copyright 2024 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

Expand Down 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