Skip to content

Commit

Permalink
fix delete special prices only for specified store
Browse files Browse the repository at this point in the history
  • Loading branch information
engcom-Charlie committed Dec 29, 2020
1 parent 1c3837c commit de30e9c
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@

namespace Magento\Catalog\Model\ResourceModel\Product\Price;

use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
use Magento\Catalog\Api\SpecialPriceInterface;
use Magento\Catalog\Helper\Data;
use Magento\Catalog\Model\ProductIdLocatorInterface;
use Magento\Catalog\Model\ResourceModel\Attribute;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\App\ObjectManager;

/**
* Special price resource.
*/
class SpecialPrice implements \Magento\Catalog\Api\SpecialPriceInterface
class SpecialPrice implements SpecialPriceInterface
{
/**
* Price storage table.
Expand All @@ -26,24 +35,24 @@ class SpecialPrice implements \Magento\Catalog\Api\SpecialPriceInterface
private $datetimeTable = 'catalog_product_entity_datetime';

/**
* @var \Magento\Catalog\Model\ResourceModel\Attribute
* @var Attribute
*/
private $attributeResource;

/**
* @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface
* @var ProductAttributeRepositoryInterface
*/
private $attributeRepository;

/**
* @var \Magento\Catalog\Model\ProductIdLocatorInterface
* @var ProductIdLocatorInterface
*/
private $productIdLocator;

/**
* Metadata pool.
*
* @var \Magento\Framework\EntityManager\MetadataPool
* @var MetadataPool
*/
private $metadataPool;

Expand All @@ -68,6 +77,11 @@ class SpecialPrice implements \Magento\Catalog\Api\SpecialPriceInterface
*/
private $priceToAttributeId;

/**
* @var Data
*/
private $catalogData;

/**
* Items per operation.
*
Expand All @@ -76,25 +90,28 @@ class SpecialPrice implements \Magento\Catalog\Api\SpecialPriceInterface
private $itemsPerOperation = 500;

/**
* @param \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource
* @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
* @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
* @param Attribute $attributeResource
* @param ProductAttributeRepositoryInterface $attributeRepository
* @param ProductIdLocatorInterface $productIdLocator
* @param MetadataPool $metadataPool
* @param Data|null $catalogData
*/
public function __construct(
\Magento\Catalog\Model\ResourceModel\Attribute $attributeResource,
\Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
\Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
\Magento\Framework\EntityManager\MetadataPool $metadataPool
Attribute $attributeResource,
ProductAttributeRepositoryInterface $attributeRepository,
ProductIdLocatorInterface $productIdLocator,
MetadataPool $metadataPool,
?Data $catalogData = null
) {
$this->attributeResource = $attributeResource;
$this->attributeRepository = $attributeRepository;
$this->productIdLocator = $productIdLocator;
$this->metadataPool = $metadataPool;
$this->catalogData = $catalogData ?: ObjectManager::getInstance()->get(Data::class);
}

/**
* {@inheritdoc}
* @inheritdoc
*/
public function get(array $skus)
{
Expand Down Expand Up @@ -132,7 +149,7 @@ public function get(array $skus)
}

/**
* {@inheritdoc}
* @inheritdoc
*/
public function update(array $prices)
{
Expand Down Expand Up @@ -187,47 +204,69 @@ public function update(array $prices)
}

/**
* {@inheritdoc}
* @inheritdoc
*/
public function delete(array $prices)
{
$skus = array_unique(
array_map(function ($price) {
return $price->getSku();
}, $prices)
);
$ids = $this->retrieveAffectedIds($skus);
$connection = $this->attributeResource->getConnection();
$connection->beginTransaction();
try {
foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
$this->attributeResource->getConnection()->delete(
$this->attributeResource->getTable($this->priceTable),
[
'attribute_id = ?' => $this->getPriceAttributeId(),
$this->getEntityLinkField() . ' IN (?)' => $idsBunch
]
);

foreach ($this->getStoreSkus($prices) as $storeId => $skus) {

$ids = $this->retrieveAffectedIds(array_unique($skus));
$connection->beginTransaction();
try {
foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
$connection->delete(
$this->attributeResource->getTable($this->priceTable),
[
'attribute_id = ?' => $this->getPriceAttributeId(),
'store_id = ?' => $storeId,
$this->getEntityLinkField() . ' IN (?)' => $idsBunch
]
);
}
foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
$connection->delete(
$this->attributeResource->getTable($this->datetimeTable),
[
'attribute_id IN (?)' => [$this->getPriceFromAttributeId(), $this->getPriceToAttributeId()],
'store_id = ?' => $storeId,
$this->getEntityLinkField() . ' IN (?)' => $idsBunch
]
);
}
$connection->commit();
} catch (\Exception $e) {
$connection->rollBack();
throw new CouldNotDeleteException(__('Could not delete Prices'), $e);
}
foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
$this->attributeResource->getConnection()->delete(
$this->attributeResource->getTable($this->datetimeTable),
[
'attribute_id IN (?)' => [$this->getPriceFromAttributeId(), $this->getPriceToAttributeId()],
$this->getEntityLinkField() . ' IN (?)' => $idsBunch
]
}

return true;
}

/**
* Returns associative arrays of store_id as key and array of skus as value.
*
* @param \Magento\Catalog\Api\Data\SpecialPriceInterface[] $priceItems
* @return array
* @throws CouldNotDeleteException
*/
private function getStoreSkus(array $priceItems): array
{
$isPriceGlobal = $this->catalogData->isPriceGlobal();

$storeSkus = [];
foreach ($priceItems as $priceItem) {
if ($isPriceGlobal && $priceItem->getStoreId() !== 0) {
throw new CouldNotDeleteException(
__('Could not delete Prices for non-default store when price scope is global.')
);
}
$connection->commit();
} catch (\Exception $e) {
$connection->rollBack();
throw new \Magento\Framework\Exception\CouldNotDeleteException(
__('Could not delete Prices'),
$e
);
$storeSkus[$priceItem->getStoreId()][] = $priceItem->getSku();
}

return true;
return $storeSkus;
}

/**
Expand Down Expand Up @@ -312,9 +351,9 @@ private function retrieveAffectedIds(array $skus)
$affectedIds = [];

foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productIds) {
$affectedIds = array_merge($affectedIds, array_keys($productIds));
$affectedIds[] = array_keys($productIds);
}

return array_unique($affectedIds);
return array_unique(array_merge([], ...$affectedIds));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@
namespace Magento\Catalog\Api;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\StateException;
use Magento\Framework\Webapi\Rest\Request;
use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\ObjectManager;
use Magento\TestFramework\TestCase\WebapiAbstract;
Expand All @@ -31,11 +28,17 @@ class SpecialPriceStorageTest extends WebapiAbstract
private $objectManager;

/**
* Set up.
* @var ProductResource
*/
private $productResource;

/**
* @ingeritdoc
*/
protected function setUp(): void
{
$this->objectManager = Bootstrap::getObjectManager();
$this->productResource = $this->objectManager->get(ProductResource::class);
}

/**
Expand Down Expand Up @@ -128,28 +131,67 @@ public function testUpdate(array $data)
$this->assertEmpty($response);
}

/**
* Delete special price for specified store when price scope is global
*
* @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
*
* @return void
*/
public function testDeleteWhenPriceIsGlobal(): void
{
$serviceInfo = [
'rest' => [
'resourcePath' => '/V1/products/special-price-delete',
'httpMethod' => Request::HTTP_METHOD_POST
],
'soap' => [
'service' => self::SERVICE_NAME,
'serviceVersion' => self::SERVICE_VERSION,
'operation' => self::SERVICE_NAME . 'Delete',
],
];

$this->expectErrorMessage('Could not delete Prices for non-default store when price scope is global.');

$this->_webApiCall(
$serviceInfo,
[
'prices' => [
['price' => 777, 'store_id' => 1, 'sku' => self::SIMPLE_PRODUCT_SKU]
]
]
);
}

/**
* Test delete method.
*
* @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
* @magentoConfigFixture catalog/price/scope 1
* @dataProvider deleteData
* @param array $data
* @throws CouldNotSaveException
* @throws InputException
* @throws NoSuchEntityException
* @throws StateException
* @return void
*/
public function testDelete(array $data)
public function testDelete(array $data): void
{
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
$product = $productRepository->get($data['sku'], true);
$product = $productRepository->get($data['sku'], true, $data['store_id'], true);
$product->setData('special_price', $data['price']);
$product->setData('special_from_date', $data['price_from']);
if ($data['price_to']) {
$product->setData('special_to_date', $data['price_to']);
}
$productRepository->save($product);
$this->productResource->saveAttribute($product, 'special_price');
$this->productResource->saveAttribute($product, 'special_from_date');
$this->productResource->saveAttribute($product, 'special_to_date');

$product->setData('store_id', 1);
$this->productResource->saveAttribute($product, 'special_price');
$this->productResource->saveAttribute($product, 'special_from_date');
$this->productResource->saveAttribute($product, 'special_to_date');

$serviceInfo = [
'rest' => [
'resourcePath' => '/V1/products/special-price-delete',
Expand All @@ -161,17 +203,21 @@ public function testDelete(array $data)
'operation' => self::SERVICE_NAME . 'Delete',
],
];

$response = $this->_webApiCall(
$serviceInfo,
[
'prices' => [
$data
]
'prices' => [$data]
]
);
$product = $productRepository->get($data['sku'], false, null, true);

$this->assertEmpty($response);

$product = $productRepository->get($data['sku'], false, $data['store_id'], true);
$this->assertNull($product->getSpecialPrice());

$product = $productRepository->get($data['sku'], false, 1, true);
$this->assertNotNull($product->getSpecialPrice());
}

/**
Expand Down Expand Up @@ -219,8 +265,7 @@ public function deleteData(): array
$toDate = '2038-01-19 03:14:07';

return [
[
// data set with 'price_to' specified
'data set with price_to specified' => [
[
'price' => 3057,
'store_id' => 0,
Expand All @@ -229,8 +274,7 @@ public function deleteData(): array
'price_to' => $toDate
]
],
[
// data set without 'price_to' specified
'data set without price_to specified' => [
[
'price' => 3057,
'store_id' => 0,
Expand Down

0 comments on commit de30e9c

Please sign in to comment.