Skip to content

Commit 8916dee

Browse files
Merge remote-tracking branch 'origin/documentation-part-1' into documentation-part-1
2 parents e7b3b4d + 67e3881 commit 8916dee

File tree

208 files changed

+6382
-656
lines changed

Some content is hidden

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

208 files changed

+6382
-656
lines changed

app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ public function execute()
3434
/** @var $export \Magento\ImportExport\Model\Export */
3535
$export = $this->_objectManager->create(\Magento\ImportExport\Model\Export::class);
3636
$export->setData($data);
37-
$export->filterAttributeCollection(
38-
$attrFilterBlock->prepareCollection($export->getEntityAttributeCollection())
37+
$attrFilterBlock->prepareCollection(
38+
$export->filterAttributeCollection($export->getEntityAttributeCollection())
3939
);
4040
return $resultLayout;
4141
} catch (\Exception $e) {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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\AdvancedPricingImportExport\Model;
9+
10+
use Magento\Directory\Helper\Data as DirectoryData;
11+
use Magento\Store\Model\StoreManagerInterface;
12+
13+
/**
14+
* Currency resolver for tier price scope
15+
*/
16+
class CurrencyResolver
17+
{
18+
/**
19+
* @var StoreManagerInterface
20+
*/
21+
private $storeManager;
22+
23+
/**
24+
* @var DirectoryData
25+
*/
26+
private $directoryData;
27+
28+
/**
29+
* @var string
30+
*/
31+
private $defaultBaseCurrency;
32+
33+
/**
34+
* Associative array with website code as the key and base currency as the value
35+
*
36+
* @var array
37+
*/
38+
private $websitesBaseCurrency;
39+
40+
/**
41+
* @param StoreManagerInterface $storeManager
42+
* @param DirectoryData $directoryData
43+
*/
44+
public function __construct(
45+
StoreManagerInterface $storeManager,
46+
DirectoryData $directoryData
47+
) {
48+
$this->storeManager = $storeManager;
49+
$this->directoryData = $directoryData;
50+
}
51+
52+
/**
53+
* Get base currency for all websites
54+
*
55+
* @return array associative array with website code as the key and base currency as the value
56+
*/
57+
public function getWebsitesBaseCurrency(): array
58+
{
59+
if ($this->websitesBaseCurrency === null) {
60+
$this->websitesBaseCurrency = [];
61+
foreach ($this->storeManager->getWebsites() as $website) {
62+
$this->websitesBaseCurrency[$website->getCode()] = $website->getBaseCurrencyCode();
63+
}
64+
}
65+
66+
return $this->websitesBaseCurrency;
67+
}
68+
69+
/**
70+
* Get default scope base currency
71+
*
72+
* @return string
73+
*/
74+
public function getDefaultBaseCurrency(): string
75+
{
76+
if ($this->defaultBaseCurrency === null) {
77+
$this->defaultBaseCurrency = $this->directoryData->getBaseCurrencyCode();
78+
}
79+
80+
return $this->defaultBaseCurrency;
81+
}
82+
}

app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php

Lines changed: 195 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
*/
66
namespace Magento\AdvancedPricingImportExport\Model\Import;
77

8+
use Magento\AdvancedPricingImportExport\Model\CurrencyResolver;
89
use Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
910
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface;
11+
use Magento\Framework\App\ObjectManager;
1012
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
1113

1214
/**
@@ -15,6 +17,7 @@
1517
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
1618
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1719
* @SuppressWarnings(PHPMD.TooManyFields)
20+
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
1821
*/
1922
class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
2023
{
@@ -42,6 +45,8 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
4245
private const VALIDATOR_TEAR_PRICE = 'validator_tier_price';
4346
private const VALIDATOR_TIER_PRICE = 'validator_tier_price';
4447

48+
private const ERROR_DUPLICATE_TIER_PRICE = 'duplicateTierPrice';
49+
4550
/**
4651
* Validation failure message template definitions.
4752
*
@@ -57,8 +62,10 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
5762
ValidatorInterface::ERROR_INVALID_TIER_PRICE_TYPE => 'Value for \'tier_price_value_type\' ' .
5863
'attribute contains incorrect value, acceptable values are Fixed, Discount',
5964
ValidatorInterface::ERROR_TIER_DATA_INCOMPLETE => 'Tier Price data is incomplete',
60-
ValidatorInterface::ERROR_INVALID_ATTRIBUTE_DECIMAL =>
61-
'Value for \'%s\' attribute contains incorrect value, acceptable values are in decimal format',
65+
ValidatorInterface::ERROR_INVALID_ATTRIBUTE_DECIMAL => 'Value for \'%s\' attribute contains incorrect value,' .
66+
' acceptable values are in decimal format',
67+
self::ERROR_DUPLICATE_TIER_PRICE => 'We found a duplicate website, tier price, customer group' .
68+
' and quantity.'
6269
];
6370

6471
/**
@@ -155,6 +162,26 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
155162
*/
156163
private $productEntityLinkField;
157164

165+
/**
166+
* @var array
167+
*/
168+
private $websiteScopeTierPrice = [];
169+
170+
/**
171+
* @var array
172+
*/
173+
private $globalScopeTierPrice = [];
174+
175+
/**
176+
* @var array
177+
*/
178+
private $allProductIds = [];
179+
180+
/**
181+
* @var CurrencyResolver
182+
*/
183+
private $currencyResolver;
184+
158185
/**
159186
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
160187
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
@@ -172,8 +199,9 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
172199
* @param AdvancedPricing\Validator $validator
173200
* @param AdvancedPricing\Validator\Website $websiteValidator
174201
* @param AdvancedPricing\Validator\TierPrice $tierPriceValidator
175-
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
202+
* @param CurrencyResolver|null $currencyResolver
176203
* @throws \Exception
204+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
177205
*/
178206
public function __construct(
179207
\Magento\Framework\Json\Helper\Data $jsonHelper,
@@ -190,7 +218,8 @@ public function __construct(
190218
ImportProduct $importProduct,
191219
AdvancedPricing\Validator $validator,
192220
AdvancedPricing\Validator\Website $websiteValidator,
193-
AdvancedPricing\Validator\TierPrice $tierPriceValidator
221+
AdvancedPricing\Validator\TierPrice $tierPriceValidator,
222+
?CurrencyResolver $currencyResolver = null
194223
) {
195224
$this->dateTime = $dateTime;
196225
$this->jsonHelper = $jsonHelper;
@@ -209,6 +238,7 @@ public function __construct(
209238
$this->_validators[self::VALIDATOR_WEBSITE] = $websiteValidator;
210239
$this->_validators[self::VALIDATOR_TIER_PRICE] = $tierPriceValidator;
211240
$this->errorAggregator = $errorAggregator;
241+
$this->currencyResolver = $currencyResolver ?? ObjectManager::getInstance()->get(CurrencyResolver::class);
212242

213243
foreach (array_merge($this->errorMessageTemplates, $this->_messageTemplates) as $errorCode => $message) {
214244
$this->getErrorAggregator()->addErrorMessageTemplate($errorCode, $message);
@@ -270,6 +300,11 @@ public function validateRow(array $rowData, $rowNum)
270300
if (false === $sku) {
271301
$this->addRowError(ValidatorInterface::ERROR_ROW_IS_ORPHAN, $rowNum);
272302
}
303+
304+
if (!$this->getErrorAggregator()->isRowInvalid($rowNum)) {
305+
$this->validateRowForDuplicate($rowData, $rowNum);
306+
}
307+
273308
return !$this->getErrorAggregator()->isRowInvalid($rowNum);
274309
}
275310

@@ -634,4 +669,160 @@ private function getProductEntityLinkField()
634669
}
635670
return $this->productEntityLinkField;
636671
}
672+
673+
/**
674+
* @inheritdoc
675+
*/
676+
protected function _saveValidatedBunches()
677+
{
678+
if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND === $this->getBehavior()
679+
&& !$this->_catalogData->isPriceGlobal()
680+
) {
681+
$source = $this->_getSource();
682+
$source->rewind();
683+
while ($source->valid()) {
684+
try {
685+
$rowData = $source->current();
686+
} catch (\InvalidArgumentException $exception) {
687+
$source->next();
688+
continue;
689+
}
690+
$this->validateRow($rowData, $source->key());
691+
$source->next();
692+
}
693+
$this->validateRowsForDuplicate(self::TABLE_TIER_PRICE);
694+
}
695+
return parent::_saveValidatedBunches();
696+
}
697+
698+
/**
699+
* Validate all row data with existing prices in the database for duplicate
700+
*
701+
* A row is considered a duplicate if the pair (product_id, all_groups, customer_group_id, qty) exists for
702+
* both global and website scopes. And the base currency is the same for both global and website scopes.
703+
*
704+
* @param string $table
705+
*/
706+
private function validateRowsForDuplicate(string $table): void
707+
{
708+
if (!empty($this->allProductIds)) {
709+
$priceDataCollection = $this->getPrices(array_keys($this->allProductIds), $table);
710+
$defaultBaseCurrency = $this->currencyResolver->getDefaultBaseCurrency();
711+
$websiteCodeBaseCurrencyMap = $this->currencyResolver->getWebsitesBaseCurrency();
712+
$websiteIdCodeMap = array_flip($this->_storeResolver->getWebsiteCodeToId());
713+
foreach ($priceDataCollection as $priceData) {
714+
$isDefaultScope = (int) $priceData['website_id'] === 0;
715+
$baseCurrency = $isDefaultScope
716+
? $defaultBaseCurrency
717+
: $websiteCodeBaseCurrencyMap[$websiteIdCodeMap[$priceData['website_id']] ?? null] ?? null;
718+
$rowNums = [];
719+
$key = $this->getUniqueKey($priceData, $baseCurrency);
720+
if ($isDefaultScope) {
721+
if (isset($this->websiteScopeTierPrice[$key])) {
722+
$rowNums = $this->websiteScopeTierPrice[$key];
723+
}
724+
} else {
725+
if (isset($this->globalScopeTierPrice[$key])) {
726+
$rowNums = $this->globalScopeTierPrice[$key];
727+
}
728+
}
729+
foreach ($rowNums as $rowNum) {
730+
$this->addRowError(self::ERROR_DUPLICATE_TIER_PRICE, $rowNum);
731+
}
732+
}
733+
}
734+
}
735+
736+
/**
737+
* Validate row data for duplicate
738+
*
739+
* A row is considered a duplicate if the pair (product_id, all_groups, customer_group_id, qty) exists for
740+
* both global and website scopes. And the base currency is the same for both global and website scopes.
741+
*
742+
* @param array $rowData
743+
* @param int $rowNum
744+
*/
745+
private function validateRowForDuplicate(array $rowData, int $rowNum)
746+
{
747+
$productId = $this->retrieveOldSkus()[$rowData[self::COL_SKU]] ?? null;
748+
if ($productId && !$this->_catalogData->isPriceGlobal()) {
749+
$productEntityLinkField = $this->getProductEntityLinkField();
750+
$priceData = [
751+
$productEntityLinkField => $productId,
752+
'website_id' => (int) $this->getWebSiteId($rowData[self::COL_TIER_PRICE_WEBSITE]),
753+
'all_groups' => $rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP] == self::VALUE_ALL_GROUPS ? 1 : 0,
754+
'customer_group_id' => $this->getCustomerGroupId($rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP]),
755+
'qty' => $rowData[self::COL_TIER_PRICE_QTY],
756+
];
757+
$defaultBaseCurrency = $this->currencyResolver->getDefaultBaseCurrency();
758+
$websiteCodeBaseCurrencyMap = $this->currencyResolver->getWebsitesBaseCurrency();
759+
$websiteIdCodeMap = array_flip($this->_storeResolver->getWebsiteCodeToId());
760+
$baseCurrency = $priceData['website_id'] === 0
761+
? $defaultBaseCurrency
762+
: $websiteCodeBaseCurrencyMap[$websiteIdCodeMap[$priceData['website_id']] ?? null] ?? null;
763+
764+
$this->allProductIds[$productId][] = $rowNum;
765+
$key = $this->getUniqueKey($priceData, $baseCurrency);
766+
if ($priceData['website_id'] === 0) {
767+
$this->globalScopeTierPrice[$key][] = $rowNum;
768+
if (isset($this->websiteScopeTierPrice[$key])) {
769+
$this->addRowError(self::ERROR_DUPLICATE_TIER_PRICE, $rowNum);
770+
}
771+
} else {
772+
$this->websiteScopeTierPrice[$key][] = $rowNum;
773+
if (isset($this->globalScopeTierPrice[$key])) {
774+
$this->addRowError(self::ERROR_DUPLICATE_TIER_PRICE, $rowNum);
775+
}
776+
}
777+
}
778+
}
779+
780+
/**
781+
* Get the unique key of provided price
782+
*
783+
* @param array $priceData
784+
* @param string $baseCurrency
785+
* @return string
786+
*/
787+
private function getUniqueKey(array $priceData, string $baseCurrency): string
788+
{
789+
$productEntityLinkField = $this->getProductEntityLinkField();
790+
return sprintf(
791+
'%s-%s-%s-%s-%.4f',
792+
$baseCurrency,
793+
$priceData[$productEntityLinkField],
794+
$priceData['all_groups'],
795+
$priceData['customer_group_id'],
796+
$priceData['qty']
797+
);
798+
}
799+
800+
/**
801+
* Get existing prices in the database
802+
*
803+
* @param int[] $productIds
804+
* @param string $table
805+
* @return array
806+
*/
807+
private function getPrices(array $productIds, string $table)
808+
{
809+
$productEntityLinkField = $this->getProductEntityLinkField();
810+
return $this->_connection->fetchAll(
811+
$this->_connection->select()
812+
->from(
813+
$this->_resourceFactory->create()->getTable($table),
814+
[
815+
$productEntityLinkField,
816+
'all_groups',
817+
'customer_group_id',
818+
'qty',
819+
'website_id'
820+
]
821+
)
822+
->where(
823+
$productEntityLinkField . ' IN (?)',
824+
$productIds
825+
)
826+
);
827+
}
637828
}

0 commit comments

Comments
 (0)