5
5
*/
6
6
namespace Magento \AdvancedPricingImportExport \Model \Import ;
7
7
8
+ use Magento \AdvancedPricingImportExport \Model \CurrencyResolver ;
8
9
use Magento \CatalogImportExport \Model \Import \Product as ImportProduct ;
9
10
use Magento \CatalogImportExport \Model \Import \Product \RowValidatorInterface as ValidatorInterface ;
11
+ use Magento \Framework \App \ObjectManager ;
10
12
use Magento \ImportExport \Model \Import \ErrorProcessing \ProcessingErrorAggregatorInterface ;
11
13
12
14
/**
15
17
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
16
18
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
17
19
* @SuppressWarnings(PHPMD.TooManyFields)
20
+ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
18
21
*/
19
22
class AdvancedPricing extends \Magento \ImportExport \Model \Import \Entity \AbstractEntity
20
23
{
@@ -42,6 +45,8 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
42
45
private const VALIDATOR_TEAR_PRICE = 'validator_tier_price ' ;
43
46
private const VALIDATOR_TIER_PRICE = 'validator_tier_price ' ;
44
47
48
+ private const ERROR_DUPLICATE_TIER_PRICE = 'duplicateTierPrice ' ;
49
+
45
50
/**
46
51
* Validation failure message template definitions.
47
52
*
@@ -57,8 +62,10 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
57
62
ValidatorInterface::ERROR_INVALID_TIER_PRICE_TYPE => 'Value for \'tier_price_value_type \' ' .
58
63
'attribute contains incorrect value, acceptable values are Fixed, Discount ' ,
59
64
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. '
62
69
];
63
70
64
71
/**
@@ -155,6 +162,26 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
155
162
*/
156
163
private $ productEntityLinkField ;
157
164
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
+
158
185
/**
159
186
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
160
187
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
@@ -172,8 +199,9 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
172
199
* @param AdvancedPricing\Validator $validator
173
200
* @param AdvancedPricing\Validator\Website $websiteValidator
174
201
* @param AdvancedPricing\Validator\TierPrice $tierPriceValidator
175
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
202
+ * @param CurrencyResolver|null $currencyResolver
176
203
* @throws \Exception
204
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
177
205
*/
178
206
public function __construct (
179
207
\Magento \Framework \Json \Helper \Data $ jsonHelper ,
@@ -190,7 +218,8 @@ public function __construct(
190
218
ImportProduct $ importProduct ,
191
219
AdvancedPricing \Validator $ validator ,
192
220
AdvancedPricing \Validator \Website $ websiteValidator ,
193
- AdvancedPricing \Validator \TierPrice $ tierPriceValidator
221
+ AdvancedPricing \Validator \TierPrice $ tierPriceValidator ,
222
+ ?CurrencyResolver $ currencyResolver = null
194
223
) {
195
224
$ this ->dateTime = $ dateTime ;
196
225
$ this ->jsonHelper = $ jsonHelper ;
@@ -209,6 +238,7 @@ public function __construct(
209
238
$ this ->_validators [self ::VALIDATOR_WEBSITE ] = $ websiteValidator ;
210
239
$ this ->_validators [self ::VALIDATOR_TIER_PRICE ] = $ tierPriceValidator ;
211
240
$ this ->errorAggregator = $ errorAggregator ;
241
+ $ this ->currencyResolver = $ currencyResolver ?? ObjectManager::getInstance ()->get (CurrencyResolver::class);
212
242
213
243
foreach (array_merge ($ this ->errorMessageTemplates , $ this ->_messageTemplates ) as $ errorCode => $ message ) {
214
244
$ this ->getErrorAggregator ()->addErrorMessageTemplate ($ errorCode , $ message );
@@ -270,6 +300,11 @@ public function validateRow(array $rowData, $rowNum)
270
300
if (false === $ sku ) {
271
301
$ this ->addRowError (ValidatorInterface::ERROR_ROW_IS_ORPHAN , $ rowNum );
272
302
}
303
+
304
+ if (!$ this ->getErrorAggregator ()->isRowInvalid ($ rowNum )) {
305
+ $ this ->validateRowForDuplicate ($ rowData , $ rowNum );
306
+ }
307
+
273
308
return !$ this ->getErrorAggregator ()->isRowInvalid ($ rowNum );
274
309
}
275
310
@@ -634,4 +669,152 @@ private function getProductEntityLinkField()
634
669
}
635
670
return $ this ->productEntityLinkField ;
636
671
}
672
+
673
+ /**
674
+ * @inheritdoc
675
+ */
676
+ public function validateData ()
677
+ {
678
+ $ isDataValidated = $ this ->_dataValidated ;
679
+ $ result = parent ::validateData ();
680
+ if (!$ isDataValidated
681
+ && $ this ->_dataValidated
682
+ && \Magento \ImportExport \Model \Import::BEHAVIOR_APPEND === $ this ->getBehavior ()
683
+ && !$ this ->_catalogData ->isPriceGlobal ()
684
+ ) {
685
+ $ this ->validateRowsForDuplicate (self ::TABLE_TIER_PRICE );
686
+ }
687
+ return $ result ;
688
+ }
689
+
690
+ /**
691
+ * Validate all row data with existing prices in the database for duplicate
692
+ *
693
+ * A row is considered a duplicate if the pair (product_id, all_groups, customer_group_id, qty) exists for
694
+ * both global and website scopes. And the base currency is the same for both global and website scopes.
695
+ *
696
+ * @param string $table
697
+ */
698
+ private function validateRowsForDuplicate (string $ table ): void
699
+ {
700
+ if (!empty ($ this ->allProductIds )) {
701
+ $ priceDataCollection = $ this ->getPrices (array_keys ($ this ->allProductIds ), $ table );
702
+ $ defaultBaseCurrency = $ this ->currencyResolver ->getDefaultBaseCurrency ();
703
+ $ websiteCodeBaseCurrencyMap = $ this ->currencyResolver ->getWebsitesBaseCurrency ();
704
+ $ websiteIdCodeMap = array_flip ($ this ->_storeResolver ->getWebsiteCodeToId ());
705
+ foreach ($ priceDataCollection as $ priceData ) {
706
+ $ isDefaultScope = (int ) $ priceData ['website_id ' ] === 0 ;
707
+ $ baseCurrency = $ isDefaultScope
708
+ ? $ defaultBaseCurrency
709
+ : $ websiteCodeBaseCurrencyMap [$ websiteIdCodeMap [$ priceData ['website_id ' ]] ?? null ] ?? null ;
710
+ $ rowNums = [];
711
+ $ key = $ this ->getUniqueKey ($ priceData , $ baseCurrency );
712
+ if ($ isDefaultScope ) {
713
+ if (isset ($ this ->websiteScopeTierPrice [$ key ])) {
714
+ $ rowNums = $ this ->websiteScopeTierPrice [$ key ];
715
+ }
716
+ } else {
717
+ if (isset ($ this ->globalScopeTierPrice [$ key ])) {
718
+ $ rowNums = $ this ->globalScopeTierPrice [$ key ];
719
+ }
720
+ }
721
+ foreach ($ rowNums as $ rowNum ) {
722
+ $ this ->addRowError (self ::ERROR_DUPLICATE_TIER_PRICE , $ rowNum );
723
+ }
724
+ }
725
+ }
726
+ }
727
+
728
+ /**
729
+ * Validate row data for duplicate
730
+ *
731
+ * A row is considered a duplicate if the pair (product_id, all_groups, customer_group_id, qty) exists for
732
+ * both global and website scopes. And the base currency is the same for both global and website scopes.
733
+ *
734
+ * @param array $rowData
735
+ * @param int $rowNum
736
+ */
737
+ private function validateRowForDuplicate (array $ rowData , int $ rowNum )
738
+ {
739
+ $ productId = $ this ->retrieveOldSkus ()[$ rowData [self ::COL_SKU ]] ?? null ;
740
+ if ($ productId && !$ this ->_catalogData ->isPriceGlobal ()) {
741
+ $ productEntityLinkField = $ this ->getProductEntityLinkField ();
742
+ $ priceData = [
743
+ $ productEntityLinkField => $ productId ,
744
+ 'website_id ' => (int ) $ this ->getWebSiteId ($ rowData [self ::COL_TIER_PRICE_WEBSITE ]),
745
+ 'all_groups ' => $ rowData [self ::COL_TIER_PRICE_CUSTOMER_GROUP ] == self ::VALUE_ALL_GROUPS ? 1 : 0 ,
746
+ 'customer_group_id ' => $ this ->getCustomerGroupId ($ rowData [self ::COL_TIER_PRICE_CUSTOMER_GROUP ]),
747
+ 'qty ' => $ rowData [self ::COL_TIER_PRICE_QTY ],
748
+ ];
749
+ $ defaultBaseCurrency = $ this ->currencyResolver ->getDefaultBaseCurrency ();
750
+ $ websiteCodeBaseCurrencyMap = $ this ->currencyResolver ->getWebsitesBaseCurrency ();
751
+ $ websiteIdCodeMap = array_flip ($ this ->_storeResolver ->getWebsiteCodeToId ());
752
+ $ baseCurrency = $ priceData ['website_id ' ] === 0
753
+ ? $ defaultBaseCurrency
754
+ : $ websiteCodeBaseCurrencyMap [$ websiteIdCodeMap [$ priceData ['website_id ' ]] ?? null ] ?? null ;
755
+
756
+ $ this ->allProductIds [$ productId ][] = $ rowNum ;
757
+ $ key = $ this ->getUniqueKey ($ priceData , $ baseCurrency );
758
+ if ($ priceData ['website_id ' ] === 0 ) {
759
+ $ this ->globalScopeTierPrice [$ key ][] = $ rowNum ;
760
+ if (isset ($ this ->websiteScopeTierPrice [$ key ])) {
761
+ $ this ->addRowError (self ::ERROR_DUPLICATE_TIER_PRICE , $ rowNum );
762
+ }
763
+ } else {
764
+ $ this ->websiteScopeTierPrice [$ key ][] = $ rowNum ;
765
+ if (isset ($ this ->globalScopeTierPrice [$ key ])) {
766
+ $ this ->addRowError (self ::ERROR_DUPLICATE_TIER_PRICE , $ rowNum );
767
+ }
768
+ }
769
+ }
770
+ }
771
+
772
+ /**
773
+ * Get the unique key of provided price
774
+ *
775
+ * @param array $priceData
776
+ * @param string $baseCurrency
777
+ * @return string
778
+ */
779
+ private function getUniqueKey (array $ priceData , string $ baseCurrency ): string
780
+ {
781
+ $ productEntityLinkField = $ this ->getProductEntityLinkField ();
782
+ return sprintf (
783
+ '%s-%s-%s-%s-%.4f ' ,
784
+ $ baseCurrency ,
785
+ $ priceData [$ productEntityLinkField ],
786
+ $ priceData ['all_groups ' ],
787
+ $ priceData ['customer_group_id ' ],
788
+ $ priceData ['qty ' ]
789
+ );
790
+ }
791
+
792
+ /**
793
+ * Get existing prices in the database
794
+ *
795
+ * @param int[] $productIds
796
+ * @param string $table
797
+ * @return array
798
+ */
799
+ private function getPrices (array $ productIds , string $ table )
800
+ {
801
+ $ productEntityLinkField = $ this ->getProductEntityLinkField ();
802
+ return $ this ->_connection ->fetchAll (
803
+ $ this ->_connection ->select ()
804
+ ->from (
805
+ $ this ->_resourceFactory ->create ()->getTable ($ table ),
806
+ [
807
+ $ productEntityLinkField ,
808
+ 'all_groups ' ,
809
+ 'customer_group_id ' ,
810
+ 'qty ' ,
811
+ 'website_id '
812
+ ]
813
+ )
814
+ ->where (
815
+ $ productEntityLinkField . ' IN (?) ' ,
816
+ $ productIds
817
+ )
818
+ );
819
+ }
637
820
}
0 commit comments