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,160 @@ private function getProductEntityLinkField()
634
669
}
635
670
return $ this ->productEntityLinkField ;
636
671
}
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
+ }
637
828
}
0 commit comments