Skip to content

Commit 884e87e

Browse files
committed
Merge remote-tracking branch 'usik2203/29168-graphql-tier-price' into HB-PR-delivery-Nov
2 parents 676a06e + 9f9164f commit 884e87e

File tree

16 files changed

+592
-138
lines changed

16 files changed

+592
-138
lines changed

app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/PriceTiers.php

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@
77

88
namespace Magento\CatalogCustomerGraphQl\Model\Resolver;
99

10+
use Magento\Catalog\Api\Data\ProductTierPriceInterface;
11+
use Magento\CatalogCustomerGraphQl\Model\Resolver\Customer\GetCustomerGroup;
12+
use Magento\CatalogCustomerGraphQl\Model\Resolver\Product\Price\Tiers;
13+
use Magento\CatalogCustomerGraphQl\Model\Resolver\Product\Price\TiersFactory;
14+
use Magento\CatalogGraphQl\Model\Resolver\Product\Price\Discount;
15+
use Magento\CatalogGraphQl\Model\Resolver\Product\Price\ProviderPool as PriceProviderPool;
16+
use Magento\Framework\Exception\LocalizedException;
1017
use Magento\Framework\GraphQl\Config\Element\Field;
18+
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
1119
use Magento\Framework\GraphQl\Query\ResolverInterface;
1220
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
13-
use Magento\Framework\Exception\LocalizedException;
14-
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
1521
use Magento\Framework\Pricing\PriceCurrencyInterface;
16-
use Magento\CatalogCustomerGraphQl\Model\Resolver\Product\Price\Tiers;
17-
use Magento\CatalogCustomerGraphQl\Model\Resolver\Product\Price\TiersFactory;
18-
use Magento\CatalogCustomerGraphQl\Model\Resolver\Customer\GetCustomerGroup;
1922
use Magento\Store\Api\Data\StoreInterface;
20-
use Magento\CatalogGraphQl\Model\Resolver\Product\Price\Discount;
21-
use Magento\CatalogGraphQl\Model\Resolver\Product\Price\ProviderPool as PriceProviderPool;
22-
use Magento\Catalog\Api\Data\ProductTierPriceInterface;
2323

2424
/**
2525
* Resolver for price_tiers
@@ -66,6 +66,16 @@ class PriceTiers implements ResolverInterface
6666
*/
6767
private $priceCurrency;
6868

69+
/**
70+
* @var array
71+
*/
72+
private $formatAndFilterTierPrices = [];
73+
74+
/**
75+
* @var array
76+
*/
77+
private $tierPricesQty = [];
78+
6979
/**
7080
* @param ValueFactory $valueFactory
7181
* @param TiersFactory $tiersFactory
@@ -115,52 +125,91 @@ public function resolve(
115125
return [];
116126
}
117127

118-
$productId = $product->getId();
128+
$productId = (int)$product->getId();
119129
$this->tiers->addProductFilter($productId);
120130

121131
return $this->valueFactory->create(
122132
function () use ($productId, $context) {
123-
/** @var StoreInterface $store */
124-
$store = $context->getExtensionAttributes()->getStore();
133+
$currencyCode = $context->getExtensionAttributes()->getStore()->getCurrentCurrencyCode();
125134

126135
$productPrice = $this->tiers->getProductRegularPrice($productId) ?? 0.0;
127136
$tierPrices = $this->tiers->getProductTierPrices($productId) ?? [];
128-
129-
return $this->formatProductTierPrices($tierPrices, $productPrice, $store);
137+
return $this->formatAndFilterTierPrices($productPrice, $tierPrices, $currencyCode);
130138
}
131139
);
132140
}
133141

134142
/**
135-
* Format tier prices for output
143+
* Format and filter tier prices for output
136144
*
137-
* @param ProductTierPriceInterface[] $tierPrices
138145
* @param float $productPrice
139-
* @param StoreInterface $store
146+
* @param ProductTierPriceInterface[] $tierPrices
147+
* @param string $currencyCode
140148
* @return array
141149
*/
142-
private function formatProductTierPrices(array $tierPrices, float $productPrice, StoreInterface $store): array
143-
{
144-
$tiers = [];
150+
private function formatAndFilterTierPrices(
151+
float $productPrice,
152+
array $tierPrices,
153+
string $currencyCode
154+
): array {
145155

146-
foreach ($tierPrices as $tierPrice) {
156+
foreach ($tierPrices as $key => $tierPrice) {
147157
$tierPrice->setValue($this->priceCurrency->convertAndRound($tierPrice->getValue()));
148-
$percentValue = $tierPrice->getExtensionAttributes()->getPercentageValue();
149-
if ($percentValue && is_numeric($percentValue)) {
150-
$discount = $this->discount->getDiscountByPercent($productPrice, (float)$percentValue);
158+
$this->formatTierPrices($productPrice, $currencyCode, $tierPrice);
159+
$this->filterTierPrices($tierPrices, $key, $tierPrice);
160+
}
161+
return $this->formatAndFilterTierPrices;
162+
}
163+
164+
/**
165+
* Format tier prices for output
166+
*
167+
* @param float $productPrice
168+
* @param string $currencyCode
169+
* @param ProductTierPriceInterface $tierPrice
170+
*/
171+
private function formatTierPrices(float $productPrice, string $currencyCode, $tierPrice)
172+
{
173+
$percentValue = $tierPrice->getExtensionAttributes()->getPercentageValue();
174+
if ($percentValue && is_numeric($percentValue)) {
175+
$discount = $this->discount->getDiscountByPercent($productPrice, (float)$percentValue);
176+
} else {
177+
$discount = $this->discount->getDiscountByDifference($productPrice, (float)$tierPrice->getValue());
178+
}
179+
180+
$this->formatAndFilterTierPrices[] = [
181+
"discount" => $discount,
182+
"quantity" => $tierPrice->getQty(),
183+
"final_price" => [
184+
"value" => $tierPrice->getValue(),
185+
"currency" => $currencyCode
186+
]
187+
];
188+
}
189+
190+
/**
191+
* Filter the lowest price for each quantity
192+
*
193+
* @param array $tierPrices
194+
* @param int $key
195+
* @param ProductTierPriceInterface $tierPriceItem
196+
*/
197+
private function filterTierPrices(
198+
array $tierPrices,
199+
int $key,
200+
ProductTierPriceInterface $tierPriceItem
201+
) {
202+
$qty = $tierPriceItem->getQty();
203+
if (isset($this->tierPricesQty[$qty])) {
204+
$priceQty = $this->tierPricesQty[$qty];
205+
if ((float)$tierPriceItem->getValue() < (float)$tierPrices[$priceQty]->getValue()) {
206+
unset($this->formatAndFilterTierPrices[$priceQty]);
207+
$this->tierPricesQty[$priceQty] = $key;
151208
} else {
152-
$discount = $this->discount->getDiscountByDifference($productPrice, (float)$tierPrice->getValue());
209+
unset($this->formatAndFilterTierPrices[$key]);
153210
}
154-
155-
$tiers[] = [
156-
"discount" => $discount,
157-
"quantity" => $tierPrice->getQty(),
158-
"final_price" => [
159-
"value" => $tierPrice->getValue(),
160-
"currency" => $store->getCurrentCurrencyCode()
161-
]
162-
];
211+
} else {
212+
$this->tierPricesQty[$qty] = $key;
163213
}
164-
return $tiers;
165214
}
166215
}

app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/Product/Price/Tiers.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function __construct(
7777
*
7878
* @param int $productId
7979
*/
80-
public function addProductFilter($productId): void
80+
public function addProductFilter(int $productId): void
8181
{
8282
$this->filterProductIds[] = $productId;
8383
}

app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/TierPrices.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public function resolve(
8383

8484
/** @var Product $product */
8585
$product = $value['model'];
86-
$productId = $product->getId();
86+
$productId = (int)$product->getId();
8787
$this->tiers->addProductFilter($productId);
8888

8989
return $this->valueFactory->create(
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogInventory\Model;
9+
10+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
11+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
12+
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
13+
use Magento\CatalogInventory\Api\StockStatusCriteriaInterfaceFactory;
14+
use Magento\CatalogInventory\Api\StockStatusRepositoryInterface;
15+
16+
/**
17+
* Preload stock data into stock registry
18+
*/
19+
class StockRegistryPreloader
20+
{
21+
/**
22+
* @var StockItemRepositoryInterface
23+
*/
24+
private $stockItemRepository;
25+
/**
26+
* @var StockConfigurationInterface
27+
*/
28+
private $stockConfiguration;
29+
/**
30+
* @var StockRegistryStorage
31+
*/
32+
private $stockRegistryStorage;
33+
/**
34+
* @var StockItemCriteriaInterfaceFactory
35+
*/
36+
private $stockItemCriteriaFactory;
37+
/**
38+
* @var StockStatusCriteriaInterfaceFactory
39+
*/
40+
private $stockStatusCriteriaFactory;
41+
/**
42+
* @var StockStatusRepositoryInterface
43+
*/
44+
private $stockStatusRepository;
45+
46+
/**
47+
* @param StockItemRepositoryInterface $stockItemRepository
48+
* @param StockStatusRepositoryInterface $stockStatusRepository
49+
* @param StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory
50+
* @param StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory
51+
* @param StockConfigurationInterface $stockConfiguration
52+
* @param StockRegistryStorage $stockRegistryStorage
53+
*/
54+
public function __construct(
55+
StockItemRepositoryInterface $stockItemRepository,
56+
StockStatusRepositoryInterface $stockStatusRepository,
57+
StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory,
58+
StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory,
59+
StockConfigurationInterface $stockConfiguration,
60+
StockRegistryStorage $stockRegistryStorage
61+
) {
62+
$this->stockItemRepository = $stockItemRepository;
63+
$this->stockStatusRepository = $stockStatusRepository;
64+
$this->stockItemCriteriaFactory = $stockItemCriteriaFactory;
65+
$this->stockStatusCriteriaFactory = $stockStatusCriteriaFactory;
66+
$this->stockConfiguration = $stockConfiguration;
67+
$this->stockRegistryStorage = $stockRegistryStorage;
68+
}
69+
70+
/**
71+
* Preload stock item into stock registry
72+
*
73+
* @param array $productIds
74+
* @param int|null $scopeId
75+
* @return \Magento\CatalogInventory\Api\Data\StockItemInterface[]
76+
*/
77+
public function preloadStockItems(array $productIds, ?int $scopeId = null): array
78+
{
79+
$scopeId = $scopeId ?? $this->stockConfiguration->getDefaultScopeId();
80+
$criteria = $this->stockItemCriteriaFactory->create();
81+
$criteria->setProductsFilter($productIds);
82+
$criteria->setScopeFilter($scopeId);
83+
$collection = $this->stockItemRepository->getList($criteria);
84+
$this->setStockItems($collection->getItems(), $scopeId);
85+
return $collection->getItems();
86+
}
87+
88+
/**
89+
* Saves stock items into registry
90+
*
91+
* @param \Magento\CatalogInventory\Api\Data\StockItemInterface[] $stockItems
92+
* @param int $scopeId
93+
*/
94+
public function setStockItems(array $stockItems, int $scopeId): void
95+
{
96+
foreach ($stockItems as $item) {
97+
$this->stockRegistryStorage->setStockItem($item->getProductId(), $scopeId, $item);
98+
}
99+
}
100+
101+
/**
102+
* Preload stock status into stock registry
103+
*
104+
* @param array $productIds
105+
* @param int|null $scopeId
106+
* @return \Magento\CatalogInventory\Api\Data\StockStatusInterface[]
107+
*/
108+
public function preloadStockStatuses(array $productIds, ?int $scopeId = null): array
109+
{
110+
$scopeId = $scopeId ?? $this->stockConfiguration->getDefaultScopeId();
111+
$criteria = $this->stockStatusCriteriaFactory->create();
112+
$criteria->setProductsFilter($productIds);
113+
$criteria->setScopeFilter($scopeId);
114+
$collection = $this->stockStatusRepository->getList($criteria);
115+
$this->setStockStatuses($collection->getItems(), $scopeId);
116+
return $collection->getItems();
117+
}
118+
119+
/**
120+
* Saves stock statuses into registry
121+
*
122+
* @param \Magento\CatalogInventory\Api\Data\StockStatusInterface[] $stockStatuses
123+
* @param int $scopeId
124+
*/
125+
public function setStockStatuses(array $stockStatuses, int $scopeId): void
126+
{
127+
foreach ($stockStatuses as $item) {
128+
$this->stockRegistryStorage->setStockStatus($item->getProductId(), $scopeId, $item);
129+
}
130+
}
131+
}

app/code/Magento/CatalogInventory/Observer/AddStockItemsObserver.php

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Magento\Catalog\Model\ResourceModel\Product\Collection;
1111
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1212
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
13-
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
13+
use Magento\CatalogInventory\Model\StockRegistryPreloader;
1414
use Magento\Framework\Event\Observer;
1515
use Magento\Framework\Event\ObserverInterface;
1616

@@ -19,36 +19,27 @@
1919
*/
2020
class AddStockItemsObserver implements ObserverInterface
2121
{
22-
/**
23-
* @var StockItemCriteriaInterfaceFactory
24-
*/
25-
private $criteriaInterfaceFactory;
26-
27-
/**
28-
* @var StockItemRepositoryInterface
29-
*/
30-
private $stockItemRepository;
31-
3222
/**
3323
* @var StockConfigurationInterface
3424
*/
3525
private $stockConfiguration;
26+
/**
27+
* @var StockRegistryPreloader
28+
*/
29+
private $stockRegistryPreloader;
3630

3731
/**
3832
* AddStockItemsObserver constructor.
3933
*
40-
* @param StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory
41-
* @param StockItemRepositoryInterface $stockItemRepository
4234
* @param StockConfigurationInterface $stockConfiguration
35+
* @param StockRegistryPreloader $stockRegistryPreloader
4336
*/
4437
public function __construct(
45-
StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory,
46-
StockItemRepositoryInterface $stockItemRepository,
47-
StockConfigurationInterface $stockConfiguration
38+
StockConfigurationInterface $stockConfiguration,
39+
StockRegistryPreloader $stockRegistryPreloader
4840
) {
49-
$this->criteriaInterfaceFactory = $criteriaInterfaceFactory;
50-
$this->stockItemRepository = $stockItemRepository;
5141
$this->stockConfiguration = $stockConfiguration;
42+
$this->stockRegistryPreloader = $stockRegistryPreloader;
5243
}
5344

5445
/**
@@ -62,11 +53,13 @@ public function execute(Observer $observer)
6253
/** @var Collection $productCollection */
6354
$productCollection = $observer->getData('collection');
6455
$productIds = array_keys($productCollection->getItems());
65-
$criteria = $this->criteriaInterfaceFactory->create();
66-
$criteria->setProductsFilter($productIds);
67-
$criteria->setScopeFilter($this->stockConfiguration->getDefaultScopeId());
68-
$stockItemCollection = $this->stockItemRepository->getList($criteria);
69-
foreach ($stockItemCollection->getItems() as $item) {
56+
$scopeId = $this->stockConfiguration->getDefaultScopeId();
57+
$stockItems = [];
58+
if ($productIds) {
59+
$stockItems = $this->stockRegistryPreloader->preloadStockItems($productIds, $scopeId);
60+
$this->stockRegistryPreloader->preloadStockStatuses($productIds, $scopeId);
61+
}
62+
foreach ($stockItems as $item) {
7063
/** @var Product $product */
7164
$product = $productCollection->getItemById($item->getProductId());
7265
$productExtension = $product->getExtensionAttributes();

0 commit comments

Comments
 (0)