Skip to content

Commit b65fe37

Browse files
committed
Merge remote-tracking branch 'origin/AC-13671' into spartans_pr_25062025
2 parents 198fb07 + 67adc27 commit b65fe37

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Plugin\SpecialPricePluginForREST;
9+
10+
use Magento\Catalog\Model\Product\Price\SpecialPriceStorage;
11+
use Magento\Store\Model\StoreManagerInterface;
12+
13+
/**
14+
* Special price storage Plugin to handle website scope issue at the frontend (only for REST API calls)
15+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
16+
*/
17+
class SpecialPriceStoragePlugin
18+
{
19+
/**
20+
* Constructor
21+
*
22+
* @param StoreManagerInterface $storeManager
23+
*/
24+
public function __construct(
25+
private StoreManagerInterface $storeManager
26+
) {
27+
}
28+
29+
/**
30+
* Around update plugin for REST api fix
31+
*
32+
* @param SpecialPriceStorage $subject
33+
* @param callable $proceed
34+
* @param array $prices
35+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
36+
*/
37+
public function aroundUpdate(SpecialPriceStorage $subject, callable $proceed, array $prices)
38+
{
39+
$prices = $this->applyWebsitePrices($prices);
40+
return $proceed($prices);
41+
}
42+
43+
/**
44+
* Function to get website id from current store id and then find all stores and apply prices to them
45+
*
46+
* @param array $formattedPrices
47+
*/
48+
private function applyWebsitePrices(array $formattedPrices): array
49+
{
50+
$newPrices = [];
51+
52+
foreach ($formattedPrices as $price) {
53+
// Add the original price first
54+
$newPrices[] = $price;
55+
56+
if ($price->getStoreId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID) {
57+
continue;
58+
}
59+
60+
$store = $this->storeManager->getStore($price->getStoreId());
61+
$website = $store->getWebsite();
62+
$storeIds = $website->getStoreIds();
63+
64+
// Unset origin store view to avoid duplication
65+
unset($storeIds[$price->getStoreId()]);
66+
67+
foreach ($storeIds as $storeId) {
68+
/** @var \Magento\Catalog\Model\Product\Price\SpecialPrice $cloned */
69+
$cloned = clone $price;
70+
$cloned->setStoreId((int) $storeId);
71+
$newPrices[] = $cloned;
72+
}
73+
}
74+
75+
return $newPrices;
76+
}
77+
}

app/code/Magento/Catalog/etc/webapi_rest/di.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,8 @@
4747
<type name="Magento\Catalog\Model\CategoryRepository">
4848
<plugin name="format_category_url_key_rest_api" type="Magento\Catalog\Plugin\Model\CategoryRepositoryPlugin" />
4949
</type>
50+
<type name="Magento\Catalog\Model\Product\Price\SpecialPriceStorage">
51+
<plugin name="vendor_special_price_rest_plugin"
52+
type="Magento\Catalog\Model\Plugin\SpecialPricePluginForREST\SpecialPriceStoragePlugin" />
53+
</type>
5054
</config>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Plugin\SpecialPricePluginForREST;
9+
10+
use Magento\Authorization\Test\Fixture\Role as RoleFixture;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
12+
use Magento\Framework\Webapi\Rest\Request;
13+
use Magento\Integration\Api\AdminTokenServiceInterface;
14+
use Magento\Store\Model\StoreManagerInterface;
15+
use Magento\TestFramework\Fixture\DataFixture;
16+
use Magento\TestFramework\Fixture\DataFixtureStorage;
17+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
18+
use Magento\TestFramework\Helper\Bootstrap;
19+
use Magento\Catalog\Test\Fixture\Product as ProductFixture;
20+
use Magento\TestFramework\TestCase\WebapiAbstract;
21+
use Magento\User\Test\Fixture\User as UserFixture;
22+
23+
/**
24+
* WebAPI test to validate SpecialPriceStoragePlugin behavior
25+
*/
26+
class SpecialPriceStoragePluginTest extends WebapiAbstract
27+
{
28+
/**
29+
* @var DataFixtureStorage
30+
*/
31+
private $fixtures;
32+
33+
/**
34+
* @var ProductRepositoryInterface
35+
*/
36+
private ProductRepositoryInterface $productRepository;
37+
38+
/**
39+
* @var StoreManagerInterface
40+
*/
41+
private StoreManagerInterface $storeManager;
42+
43+
protected function setUp(): void
44+
{
45+
$this->fixtures = Bootstrap::getObjectManager()->get(DataFixtureStorageManager::class)->getStorage();
46+
$objectManager = Bootstrap::getObjectManager();
47+
$this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
48+
$this->storeManager = $objectManager->get(StoreManagerInterface::class);
49+
}
50+
51+
#[
52+
DataFixture(ProductFixture::class, as: 'product'),
53+
DataFixture(RoleFixture::class, as: 'restrictedRole'),
54+
DataFixture(UserFixture::class, ['role_id' => '$restrictedRole.id$'], 'restrictedUser'),
55+
]
56+
public function testSpecialPriceIsAppliedToAllStoresInWebsite(): void
57+
{
58+
$this->_markTestAsRestOnly();
59+
$product = $this->fixtures->get('product');
60+
$sku = $product->getSku();
61+
$storeId= $product->getStoreId();
62+
$product->setSpecialPrice(123.45);
63+
64+
$Store = $this->storeManager->getStore($storeId);
65+
$website = $Store->getWebsite();
66+
$storeIds = $website->getStoreIds();
67+
68+
$restrictedUser = $this->fixtures->get('restrictedUser');
69+
70+
$adminTokens = Bootstrap::getObjectManager()->get(AdminTokenServiceInterface::class);
71+
$accessToken = $adminTokens->createAdminAccessToken(
72+
$restrictedUser->getData('username'),
73+
\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD
74+
);
75+
76+
$data = [
77+
'sku' => $sku,
78+
'price' =>123.45,
79+
'store_id' => $storeId
80+
];
81+
82+
$serviceInfo = [
83+
'rest' => [
84+
'resourcePath' => '/V1/products/special-price',
85+
'httpMethod' => Request::HTTP_METHOD_POST,
86+
'token' => $accessToken,
87+
],
88+
];
89+
$this->_webApiCall(
90+
$serviceInfo,
91+
[
92+
'prices' => [
93+
$data
94+
]
95+
]
96+
);
97+
foreach ($storeIds as $storeId) {
98+
$product = $this->productRepository->get($sku, false, $storeId);
99+
$this->assertNotNull(
100+
$product->getSpecialPrice(),
101+
"Expected special price for SKU '$sku' in store ID $storeId, but got none."
102+
);
103+
$this->assertEquals(
104+
123.45,
105+
(float)$product->getSpecialPrice(),
106+
"Special price mismatch for store ID $storeId"
107+
);
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)