Skip to content

Commit a4fbf70

Browse files
authored
Merge pull request #8913 from adobe-commerce-tier-4/04-29-24-Tier4-Bugfix-Delivery
Tier 4 Bugfix Delivery [04/29/24]
2 parents 4d520b5 + 652e119 commit a4fbf70

File tree

52 files changed

+1568
-506
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1568
-506
lines changed

app/code/Magento/Bundle/Model/Quote/Item/Option.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use Magento\Bundle\Model\Product\Price;
1111
use Magento\Bundle\Model\Product\Type;
1212
use Magento\Catalog\Model\Product;
13+
use Magento\Framework\App\ObjectManager;
14+
use Magento\Framework\Pricing\PriceCurrencyInterface;
1315
use Magento\Framework\Serialize\Serializer\Json;
1416

1517
/**
@@ -22,13 +24,21 @@ class Option
2224
*/
2325
private $serializer;
2426

27+
/**
28+
* @var PriceCurrencyInterface
29+
*/
30+
private $priceCurrency;
31+
2532
/**
2633
* @param Json $serializer
34+
* @param PriceCurrencyInterface $priceCurrency
2735
*/
2836
public function __construct(
29-
Json $serializer
37+
Json $serializer,
38+
?PriceCurrencyInterface $priceCurrency = null,
3039
) {
3140
$this->serializer = $serializer;
41+
$this->priceCurrency = $priceCurrency ?? ObjectManager::getInstance()->get(PriceCurrencyInterface::class);
3242
}
3343

3444
/**
@@ -83,7 +93,7 @@ private function getBundleSelectionAttributes(Product $product, Product $selecti
8393
'code' => 'bundle_selection_attributes',
8494
'value'=> $this->serializer->serialize(
8595
[
86-
'price' => $price,
96+
'price' => $this->priceCurrency->convert($price, $product->getStore()),
8797
'qty' => $qty,
8898
'option_label' => $bundleOption->getTitle(),
8999
'option_id' => $bundleOption->getId(),

app/code/Magento/Bundle/Test/Unit/Model/Quote/Item/OptionTest.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Magento\Catalog\Model\Product;
1717
use Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface;
1818
use Magento\Framework\Serialize\Serializer\Json;
19+
use Magento\Framework\Pricing\PriceCurrencyInterface;
1920
use PHPUnit\Framework\TestCase;
2021

2122
/**
@@ -34,8 +35,14 @@ class OptionTest extends TestCase
3435
protected function setUp(): void
3536
{
3637
parent::setUp();
38+
$priceCurrency = $this->createMock(PriceCurrencyInterface::class);
39+
40+
$priceCurrency->method('convert')
41+
->willReturnArgument(0);
42+
3743
$this->model = new Option(
38-
new Json()
44+
new Json(),
45+
$priceCurrency
3946
);
4047
}
4148

@@ -48,7 +55,7 @@ public function testGetSelectionOptions(array $customOptions, array $expected):
4855
{
4956
$bundleProduct = $this->getMockBuilder(Product::class)
5057
->disableOriginalConstructor()
51-
->onlyMethods(['getTypeInstance', 'getPriceModel'])
58+
->onlyMethods(['getTypeInstance', 'getPriceModel', 'getStore'])
5259
->getMock();
5360

5461
$typeInstance = $this->createMock(Type::class);

app/code/Magento/Catalog/Block/Product/ListProduct.php

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
use Magento\Catalog\Model\Product;
1717
use Magento\Catalog\Model\ResourceModel\Product\Collection;
1818
use Magento\Catalog\Pricing\Price\FinalPrice;
19+
use Magento\Catalog\Pricing\Price\SpecialPriceBulkResolverInterface;
1920
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
2021
use Magento\Framework\App\ActionInterface;
2122
use Magento\Framework\App\Config\Element;
2223
use Magento\Framework\Data\Helper\PostHelper;
2324
use Magento\Framework\DataObject\IdentityInterface;
25+
use Magento\Framework\Exception\LocalizedException;
2426
use Magento\Framework\Exception\NoSuchEntityException;
2527
use Magento\Framework\Pricing\Render;
2628
use Magento\Framework\Url\Helper\Data;
@@ -43,15 +45,11 @@ class ListProduct extends AbstractProduct implements IdentityInterface
4345
protected $_defaultToolbarBlock = Toolbar::class;
4446

4547
/**
46-
* Product Collection
47-
*
4848
* @var AbstractCollection
4949
*/
5050
protected $_productCollection;
5151

5252
/**
53-
* Catalog layer
54-
*
5553
* @var Layer
5654
*/
5755
protected $_catalogLayer;
@@ -71,6 +69,16 @@ class ListProduct extends AbstractProduct implements IdentityInterface
7169
*/
7270
protected $categoryRepository;
7371

72+
/**
73+
* @var SpecialPriceBulkResolverInterface
74+
*/
75+
private SpecialPriceBulkResolverInterface $specialPriceBulkResolver;
76+
77+
/**
78+
* @var array|null
79+
*/
80+
private ?array $specialPriceMap = null;
81+
7482
/**
7583
* @param Context $context
7684
* @param PostHelper $postDataHelper
@@ -79,6 +87,7 @@ class ListProduct extends AbstractProduct implements IdentityInterface
7987
* @param Data $urlHelper
8088
* @param array $data
8189
* @param OutputHelper|null $outputHelper
90+
* @param SpecialPriceBulkResolverInterface|null $specialPriceBulkResolver
8291
*/
8392
public function __construct(
8493
Context $context,
@@ -87,12 +96,15 @@ public function __construct(
8796
CategoryRepositoryInterface $categoryRepository,
8897
Data $urlHelper,
8998
array $data = [],
90-
?OutputHelper $outputHelper = null
99+
?OutputHelper $outputHelper = null,
100+
?SpecialPriceBulkResolverInterface $specialPriceBulkResolver = null
91101
) {
92102
$this->_catalogLayer = $layerResolver->get();
93103
$this->_postDataHelper = $postDataHelper;
94104
$this->categoryRepository = $categoryRepository;
95105
$this->urlHelper = $urlHelper;
106+
$this->specialPriceBulkResolver = $specialPriceBulkResolver ??
107+
ObjectManager::getInstance()->get(SpecialPriceBulkResolverInterface::class);
96108
$data['outputHelper'] = $outputHelper ?? ObjectManager::getInstance()->get(OutputHelper::class);
97109
parent::__construct(
98110
$context,
@@ -424,11 +436,21 @@ public function getProductPrice(Product $product)
424436
* (rendering happens in the scope of product list, but not single product)
425437
*
426438
* @return Render
439+
* @throws LocalizedException
427440
*/
428441
protected function getPriceRender()
429442
{
430-
return $this->getLayout()->getBlock('product.price.render.default')
431-
->setData('is_product_list', true);
443+
$block = $this->getLayout()->getBlock('product.price.render.default');
444+
$block->setData('is_product_list', true);
445+
446+
if ($this->specialPriceMap === null) {
447+
$this->specialPriceMap = $this->specialPriceBulkResolver->generateSpecialPriceMap(
448+
(int)$this->_storeManager->getStore()->getId(),
449+
$this->_getProductCollection()
450+
);
451+
}
452+
453+
return $block->setData('special_price_map', $this->specialPriceMap);
432454
}
433455

434456
/**

app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,9 @@ public function preparePriceData(array $priceData, $productTypeId, $websiteId)
296296
} elseif ($v['website_id'] == 0 && !isset($data[$key])) {
297297
$data[$key] = $v;
298298
$data[$key]['website_id'] = $websiteId;
299-
if ($this->_isPriceFixed($price)) {
299+
if ($this->_isPriceFixed($price) &&
300+
$this->isPercentageValue($data[$key])
301+
) {
300302
$data[$key]['price'] = $v['price'] * $rates[$websiteId]['rate'];
301303
$data[$key]['website_price'] = $v['price'] * $rates[$websiteId]['rate'];
302304
}
@@ -466,4 +468,15 @@ private function getMetadataPool()
466468
}
467469
return $this->metadataPool;
468470
}
471+
472+
/**
473+
* Check if data consists of percentage value
474+
*
475+
* @param array $data
476+
* @return bool
477+
*/
478+
private function isPercentageValue(array $data): bool
479+
{
480+
return (array_key_exists('percentage_value', $data) && $data['percentage_value'] === null);
481+
}
469482
}

app/code/Magento/Catalog/Model/ProductLink/Repository.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,14 @@ public function __construct(
133133
*/
134134
public function save(\Magento\Catalog\Api\Data\ProductLinkInterface $entity)
135135
{
136+
if (!$entity->getSku()) {
137+
throw new CouldNotSaveException(__(
138+
'The parent product SKU is required for linking child products. '
139+
. 'Please ensure the parent product SKU is provided and try again.'
140+
));
141+
}
136142
if (!$entity->getLinkedProductSku()) {
137-
throw new NoSuchEntityException(__('The linked product SKU is invalid. Verify the data and try again.'));
143+
throw new CouldNotSaveException(__('The linked product SKU is invalid. Verify the data and try again.'));
138144
}
139145
$linkedProduct = $this->productRepository->get($entity->getLinkedProductSku());
140146
$product = $this->productRepository->get($entity->getSku());
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\Catalog\Pricing\Price;
20+
21+
use Magento\Catalog\Api\Data\ProductInterface;
22+
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
23+
use Magento\Framework\App\ResourceConnection;
24+
use Magento\Framework\EntityManager\MetadataPool;
25+
use Magento\Framework\Session\SessionManagerInterface;
26+
use Magento\Framework\View\Element\Block\ArgumentInterface;
27+
28+
class SpecialPriceBulkResolver implements SpecialPriceBulkResolverInterface, ArgumentInterface
29+
{
30+
/**
31+
* @var ResourceConnection
32+
*/
33+
private ResourceConnection $resource;
34+
35+
/**
36+
* @var MetadataPool
37+
*/
38+
private MetadataPool $metadataPool;
39+
40+
/**
41+
* @var SessionManagerInterface
42+
*/
43+
private SessionManagerInterface $customerSession;
44+
45+
/**
46+
* @param ResourceConnection $resource
47+
* @param MetadataPool $metadataPool
48+
* @param SessionManagerInterface $customerSession
49+
*/
50+
public function __construct(
51+
ResourceConnection $resource,
52+
MetadataPool $metadataPool,
53+
SessionManagerInterface $customerSession
54+
) {
55+
$this->resource = $resource;
56+
$this->metadataPool = $metadataPool;
57+
$this->customerSession = $customerSession;
58+
}
59+
60+
/**
61+
* Determines if blocks have special prices
62+
*
63+
* @param int $storeId
64+
* @param AbstractCollection|null $productCollection
65+
* @return array
66+
* @throws \Exception
67+
*/
68+
public function generateSpecialPriceMap(int $storeId, ?AbstractCollection $productCollection): array
69+
{
70+
if (!$productCollection) {
71+
return [];
72+
}
73+
74+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
75+
$connection = $this->resource->getConnection();
76+
$select = $connection->select()
77+
->from(
78+
['e' => $this->resource->getTableName('catalog_product_entity')]
79+
)
80+
->joinLeft(
81+
['link' => $this->resource->getTableName('catalog_product_super_link')],
82+
'link.parent_id = e.' . $metadata->getLinkField()
83+
)
84+
->joinLeft(
85+
['product_website' => $this->resource->getTableName('catalog_product_website')],
86+
'product_website.product_id = link.product_id'
87+
)
88+
->joinLeft(
89+
['price' => $this->resource->getTableName('catalog_product_index_price')],
90+
'price.entity_id = COALESCE(link.product_id, e.entity_id) AND price.website_id = ' . $storeId .
91+
' AND price.customer_group_id = ' . $this->customerSession->getCustomerGroupId()
92+
)
93+
->where('e.entity_id IN (' . implode(',', $productCollection->getAllIds()) . ')')
94+
->columns(
95+
[
96+
'link.product_id',
97+
'(price.final_price < price.price) AS hasSpecialPrice',
98+
'e.' . $metadata->getLinkField() . ' AS identifier',
99+
'e.entity_id'
100+
]
101+
);
102+
$data = $connection->fetchAll($select);
103+
$map = [];
104+
foreach ($data as $specialPriceInfo) {
105+
if (!isset($map[$specialPriceInfo['entity_id']])) {
106+
$map[$specialPriceInfo['entity_id']] = (bool) $specialPriceInfo['hasSpecialPrice'];
107+
} else {
108+
if ($specialPriceInfo['hasSpecialPrice'] > $map[$specialPriceInfo['entity_id']]) {
109+
$map[$specialPriceInfo['entity_id']] = true;
110+
}
111+
}
112+
113+
}
114+
115+
return $map;
116+
}
117+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\Catalog\Pricing\Price;
20+
21+
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
22+
23+
interface SpecialPriceBulkResolverInterface
24+
{
25+
public const DEFAULT_CACHE_LIFE_TIME = 31536000;
26+
27+
/**
28+
* Generate special price flag for entire product listing
29+
*
30+
* @param int $storeId
31+
* @param AbstractCollection|null $productCollection
32+
* @return array
33+
*/
34+
public function generateSpecialPriceMap(int $storeId, ?AbstractCollection $productCollection): array;
35+
}

app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,18 @@ public function renderAmountMinimal()
153153
*/
154154
public function hasSpecialPrice()
155155
{
156-
$displayRegularPrice = $this->getPriceType(Price\RegularPrice::PRICE_CODE)->getAmount()->getValue();
157-
$displayFinalPrice = $this->getPriceType(Price\FinalPrice::PRICE_CODE)->getAmount()->getValue();
158-
return $displayFinalPrice < $displayRegularPrice;
156+
if ($this->isProductList()) {
157+
if (!$this->getData('special_price_map')) {
158+
return false;
159+
}
160+
161+
return (bool)$this->getData('special_price_map')[$this->saleableItem->getId()];
162+
} else {
163+
$displayRegularPrice = $this->getPriceType(Price\RegularPrice::PRICE_CODE)->getAmount()->getValue();
164+
$displayFinalPrice = $this->getPriceType(Price\FinalPrice::PRICE_CODE)->getAmount()->getValue();
165+
166+
return $displayFinalPrice < $displayRegularPrice;
167+
}
159168
}
160169

161170
/**

0 commit comments

Comments
 (0)