From d30c770c58438c9e9d6b62a05ee6b58b44812625 Mon Sep 17 00:00:00 2001
From: pradeep1819 <pradeep05.pro@gmail.com>
Date: Tue, 7 Nov 2023 12:12:56 +0530
Subject: [PATCH 01/15] ACP2E-2261: [Cloud] Product import fails with custom
 separator value

---
 .../Model/Import/Product.php                  |  5 +-
 .../Import/Product/Type/AbstractType.php      | 14 ++++-
 .../Model/Import/Product/Validator.php        | 26 +++++++--
 .../Model/Import/Product/ValidatorTest.php    | 43 +++++++++++----
 .../Test/Unit/Model/Import/ProductTest.php    | 53 +++++++++++++++++++
 .../ProductTest/ProductValidationTest.php     |  2 +-
 ...th_custom_multiselect_values_separator.csv |  2 +-
 7 files changed, 126 insertions(+), 19 deletions(-)

diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 4c0e54b5c5a05..718bc654fc971 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -53,7 +53,6 @@
 class Product extends AbstractEntity
 {
     private const COL_NAME_FORMAT = '/[\x00-\x1F\x7F]/';
-    private const DEFAULT_GLOBAL_MULTIPLE_VALUE_SEPARATOR = ',';
     public const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types';
 
     /**
@@ -2949,10 +2948,10 @@ private function parseAttributesWithWrappedValues($attributesData)
      * @return array
      * @since 100.1.2
      */
-    public function parseMultiselectValues($values, $delimiter = self::PSEUDO_MULTI_LINE_SEPARATOR)
+    public function parseMultiselectValues($values, $delimiter = '')
     {
         if (empty($this->_parameters[Import::FIELDS_ENCLOSURE])) {
-            if ($this->getMultipleValueSeparator() !== self::DEFAULT_GLOBAL_MULTIPLE_VALUE_SEPARATOR) {
+            if (!$delimiter) {
                 $delimiter = $this->getMultipleValueSeparator();
             }
 
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php
index 862cd89e3bda9..bde681768b846 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php
@@ -633,7 +633,19 @@ public function prepareAttributesWithDefaultValueForSave(array $rowData, $withDe
                     $resultAttrs[$attrCode] = $attrParams['options'][strtolower($rowData[$attrCode])];
                 } elseif ('multiselect' == $attrParams['type']) {
                     $resultAttrs[$attrCode] = [];
-                    foreach ($this->_entityModel->parseMultiselectValues($rowData[$attrCode]) as $value) {
+                    $delimiter = '';
+                    if (is_string($rowData[$attrCode])
+                        && str_contains($rowData[$attrCode], Product::PSEUDO_MULTI_LINE_SEPARATOR)) {
+                        if (!empty($rowData['additional_attributes'])
+                            && str_contains(
+                                $rowData['additional_attributes'],
+                                $attrCode . Product::PAIR_NAME_VALUE_SEPARATOR
+                            )
+                        ) {
+                            $delimiter = Product::PSEUDO_MULTI_LINE_SEPARATOR;
+                        }
+                    }
+                    foreach ($this->_entityModel->parseMultiselectValues($rowData[$attrCode], $delimiter) as $value) {
                         $resultAttrs[$attrCode][] = $attrParams['options'][strtolower($value)];
                     }
                     $resultAttrs[$attrCode] = implode(',', $resultAttrs[$attrCode]);
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
index f74886069d501..4116cd894b1d7 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
@@ -8,6 +8,7 @@
 use Magento\CatalogImportExport\Model\Import\Product;
 use Magento\Framework\Validator\AbstractValidator;
 use Magento\Catalog\Model\Product\Attribute\Backend\Sku;
+use Magento\ImportExport\Model\Import;
 
 /**
  * Product import model validator
@@ -254,11 +255,23 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData)
      */
     private function validateByAttributeType(string $attrCode, array $attrParams, array $rowData): bool
     {
+        $delimiter = '';
+        if (is_string($rowData[$attrCode]) && str_contains($rowData[$attrCode], Product::PSEUDO_MULTI_LINE_SEPARATOR)) {
+            if (!empty($rowData['additional_attributes'])
+                && str_contains($rowData['additional_attributes'], $attrCode . Product::PAIR_NAME_VALUE_SEPARATOR)) {
+                $delimiter = Product::PSEUDO_MULTI_LINE_SEPARATOR;
+            }
+        }
         return match ($attrParams['type']) {
             'varchar', 'text' => $this->textValidation($attrCode, $attrParams['type']),
             'decimal', 'int' => $this->numericValidation($attrCode, $attrParams['type']),
             'select', 'boolean' => $this->validateOption($attrCode, $attrParams['options'], $rowData[$attrCode]),
-            'multiselect' => $this->validateMultiselect($attrCode, $attrParams['options'], $rowData[$attrCode]),
+            'multiselect' => $this->validateMultiselect(
+                $attrCode,
+                $attrParams['options'],
+                $rowData[$attrCode],
+                $delimiter
+            ),
             'datetime' => $this->validateDateTime($rowData[$attrCode]),
             default => true,
         };
@@ -270,13 +283,18 @@ private function validateByAttributeType(string $attrCode, array $attrParams, ar
      * @param string $attrCode
      * @param array $options
      * @param array|string $rowData
+     * @param string $delimiter
      * @return bool
      */
-    private function validateMultiselect(string $attrCode, array $options, array|string $rowData): bool
-    {
+    private function validateMultiselect(
+        string $attrCode,
+        array $options,
+        array|string $rowData,
+        string $delimiter = ''
+    ): bool {
         $valid = true;
 
-        $values = $this->context->parseMultiselectValues($rowData);
+        $values = $this->context->parseMultiselectValues($rowData, $delimiter);
         foreach ($values as $value) {
             $valid = $this->validateOption($attrCode, $options, $value);
             if (!$valid) {
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
index d708850fe0d2f..0a46bf26d440c 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
@@ -169,33 +169,58 @@ public function attributeValidationProvider()
             [
                 Import::BEHAVIOR_APPEND,
                 ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0, 'option 2' => 1]],
-                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2|Option 3'],
+                [
+                    'product_type' => 'any',
+                    'attribute_code' => 'Option 1|Option 2|Option 3',
+                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 1|Option 2|Option 3'
+                ],
                 false
             ],
             [
                 Import::BEHAVIOR_APPEND,
                 ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0, 'option 2' => 1]],
-                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2'],
+                [
+                    'product_type' => 'any',
+                    'attribute_code' => 'Option 1|Option 2',
+                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 1|Option 2'
+                ],
                 true
             ],
             [
                 Import::BEHAVIOR_APPEND,
-                ['is_required' => true, 'type' => 'multiselect',
-                    'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']],
-                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2|Option 1'],
+                [
+                    'is_required' => true,
+                    'type' => 'multiselect',
+                    'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']
+                ],
+                [
+                    'product_type' => 'any',
+                    'attribute_code' => 'Option 1|Option 2|Option 1',
+                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 1|Option 2|Option 1'
+                ],
                 false
             ],
             [
                 Import::BEHAVIOR_APPEND,
-                ['is_required' => true, 'type' => 'multiselect',
-                    'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']],
-                ['product_type' => 'any', 'attribute_code' => 'Option 3|Option 3|Option 3|Option 1'],
+                [
+                    'is_required' => true, 'type' => 'multiselect',
+                    'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']
+                ],
+                [
+                    'product_type' => 'any',
+                    'attribute_code' => 'Option 3|Option 3|Option 3|Option 1',
+                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 3|Option 3|Option 3|Option 1'
+                ],
                 false
             ],
             [
                 Import::BEHAVIOR_APPEND,
                 ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0]],
-                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 1|Option 1|Option 1'],
+                [
+                    'product_type' => 'any',
+                    'attribute_code' => 'Option 1|Option 1|Option 1|Option 1',
+                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 1|Option 1|Option 1|Option 1'
+                ],
                 false
             ],
             [
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
index 730f736685028..a35d76a8bee1e 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
@@ -2191,4 +2191,57 @@ protected function createModelMockWithErrorAggregator(
 
         return $importProduct;
     }
+
+    /**
+     * @dataProvider valuesDataProvider
+     */
+    public function testParseMultiselectValues($value, $fieldSeparator, $valueSeparator)
+    {
+        $this->importProduct->setParameters(
+            [
+                Import::FIELD_FIELD_SEPARATOR => $fieldSeparator,
+                Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR => $valueSeparator
+            ]
+        );
+        $this->assertEquals(explode($valueSeparator, $value), $this->importProduct->parseMultiselectValues($value));
+    }
+
+    /**
+     * @return array
+     */
+    public function valuesDataProvider(): array
+    {
+        return [
+            'pipeWithCustomFieldSeparator' => [
+                'value' => 'L|C|D|T|H',
+                'fieldSeparator' => ';',
+                'valueSeparator' => '|'
+            ],
+            'commaWithCustomFieldSeparator' => [
+                'value' => 'L,C,D,T,H',
+                'fieldSeparator' => ';',
+                'valueSeparator' => ','
+            ],
+            'pipeWithDefaultFieldSeparator' => [
+                'value' => 'L|C|D|T|H',
+                'fieldSeparator' => ',',
+                'valueSeparator' => '|'
+            ],
+            'commaWithDefaultFieldSeparator' => [
+                'value' => 'L,C,D,T,H',
+                'fieldSeparator' => ',',
+                'valueSeparator' => ','
+            ],
+            'anonymousValueSeparatorWithDefaultFieldSeparator' => [
+                'value' => 'L+C+D+T+H',
+                'fieldSeparator' => ',',
+                'valueSeparator' => '+'
+            ],
+            'anonymousValueSeparatorWithDefaultFieldSeparatorAndSingleValue' => [
+                'value' => 'L',
+                'fieldSeparator' => ',',
+                'valueSeparator' => '*'
+            ]
+        ];
+    }
 }
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductValidationTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductValidationTest.php
index 2a235ab8587c8..b7798853f2c90 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductValidationTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductValidationTest.php
@@ -392,7 +392,7 @@ public function testValidateMultiselectValuesWithCustomSeparator(): void
         $params = [
             'behavior' => Import::BEHAVIOR_ADD_UPDATE,
             'entity' => 'catalog_product',
-            Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR => '|||'
+            Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR => '###'
         ];
 
         $errors = $this->_model->setParameters($params)
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_custom_multiselect_values_separator.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_custom_multiselect_values_separator.csv
index 9a0549efcb860..e14bd5ce2ea71 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_custom_multiselect_values_separator.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_custom_multiselect_values_separator.csv
@@ -1,2 +1,2 @@
 sku,store_view_code,product_type,name,price,additional_attributes
-simple_ms_2,,simple,"With Multiselect 2",10,"multiselect_attribute=Option 2|||Option 3"
+simple_ms_2,,simple,"With Multiselect 2",10,"multiselect_attribute=Option 2###Option 3"

From aebc470bb675276e296ecf72013655fe1778b863 Mon Sep 17 00:00:00 2001
From: pradeep1819 <pradeep05.pro@gmail.com>
Date: Thu, 16 Nov 2023 22:00:21 +0530
Subject: [PATCH 02/15] ACP2E-2261: [Cloud] Product import fails with custom
 separator value

---
 .../Model/Import/Product.php                  | 31 ++++++++++++++-----
 .../Import/Product/Type/AbstractType.php      | 14 +--------
 .../Model/Import/Product/Validator.php        | 20 ++----------
 3 files changed, 27 insertions(+), 38 deletions(-)

diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 718bc654fc971..5d65fa3cc4ea8 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -2836,7 +2836,7 @@ private function prepareNewSkuData($sku)
      *
      * @return array
      */
-    private function _parseAdditionalAttributes($rowData)
+    private function _parseAdditionalAttributes(array $rowData): array
     {
         if (empty($rowData['additional_attributes'])) {
             return $rowData;
@@ -2846,7 +2846,7 @@ private function _parseAdditionalAttributes($rowData)
                 $rowData[mb_strtolower($key)] = $value;
             }
         } else {
-            $rowData = array_merge($rowData, $this->getAdditionalAttributes($rowData['additional_attributes']));
+            $rowData = array_merge($rowData, $this->getAdditionalAttributes($rowData));
         }
         return $rowData;
     }
@@ -2860,14 +2860,14 @@ private function _parseAdditionalAttributes($rowData)
      *      codeN => valueN
      * ]
      *
-     * @param string $additionalAttributes Attributes data that will be parsed
+     * @param array $rowData
      * @return array
      */
-    private function getAdditionalAttributes($additionalAttributes)
+    private function getAdditionalAttributes(array $rowData): array
     {
         return empty($this->_parameters[Import::FIELDS_ENCLOSURE])
-            ? $this->parseAttributesWithoutWrappedValues($additionalAttributes)
-            : $this->parseAttributesWithWrappedValues($additionalAttributes);
+            ? $this->parseAttributesWithoutWrappedValues($rowData['additional_attributes'], $rowData['product_type'])
+            : $this->parseAttributesWithWrappedValues($rowData['additional_attributes']);
     }
 
     /**
@@ -2881,9 +2881,10 @@ private function getAdditionalAttributes($additionalAttributes)
      *
      * @param string $attributesData Attributes data that will be parsed. It keeps data in format:
      *      code=value,code2=value2...,codeN=valueN
+     * @param string $productType
      * @return array
      */
-    private function parseAttributesWithoutWrappedValues($attributesData)
+    private function parseAttributesWithoutWrappedValues(string $attributesData, string $productType): array
     {
         $attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $attributesData);
         $preparedAttributes = [];
@@ -2899,7 +2900,21 @@ private function parseAttributesWithoutWrappedValues($attributesData)
             }
             list($code, $value) = explode(self::PAIR_NAME_VALUE_SEPARATOR, $attributeData, 2);
             $code = mb_strtolower($code);
-            $preparedAttributes[$code] = $value;
+
+            $entityTypeModel = $this->retrieveProductTypeByName($productType);
+            if ($entityTypeModel) {
+                $attrParams = $entityTypeModel->retrieveAttributeFromCache($code);
+                if (!empty($attrParams) && $attrParams['type'] ==  'multiselect') {
+                    $attributeValue = $this->parseMultiselectValues($value, Product::PSEUDO_MULTI_LINE_SEPARATOR);
+                    if (count($attributeValue) > 1) {
+                        $preparedAttributes[$code] = $attributeValue;
+                    } else {
+                        $preparedAttributes[$code] = $value;
+                    }
+                }
+            } else {
+                $preparedAttributes[$code] = $value;
+            }
         }
         return $preparedAttributes;
     }
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php
index bde681768b846..862cd89e3bda9 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php
@@ -633,19 +633,7 @@ public function prepareAttributesWithDefaultValueForSave(array $rowData, $withDe
                     $resultAttrs[$attrCode] = $attrParams['options'][strtolower($rowData[$attrCode])];
                 } elseif ('multiselect' == $attrParams['type']) {
                     $resultAttrs[$attrCode] = [];
-                    $delimiter = '';
-                    if (is_string($rowData[$attrCode])
-                        && str_contains($rowData[$attrCode], Product::PSEUDO_MULTI_LINE_SEPARATOR)) {
-                        if (!empty($rowData['additional_attributes'])
-                            && str_contains(
-                                $rowData['additional_attributes'],
-                                $attrCode . Product::PAIR_NAME_VALUE_SEPARATOR
-                            )
-                        ) {
-                            $delimiter = Product::PSEUDO_MULTI_LINE_SEPARATOR;
-                        }
-                    }
-                    foreach ($this->_entityModel->parseMultiselectValues($rowData[$attrCode], $delimiter) as $value) {
+                    foreach ($this->_entityModel->parseMultiselectValues($rowData[$attrCode]) as $value) {
                         $resultAttrs[$attrCode][] = $attrParams['options'][strtolower($value)];
                     }
                     $resultAttrs[$attrCode] = implode(',', $resultAttrs[$attrCode]);
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
index 4116cd894b1d7..b4fae5f821ae4 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
@@ -255,23 +255,11 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData)
      */
     private function validateByAttributeType(string $attrCode, array $attrParams, array $rowData): bool
     {
-        $delimiter = '';
-        if (is_string($rowData[$attrCode]) && str_contains($rowData[$attrCode], Product::PSEUDO_MULTI_LINE_SEPARATOR)) {
-            if (!empty($rowData['additional_attributes'])
-                && str_contains($rowData['additional_attributes'], $attrCode . Product::PAIR_NAME_VALUE_SEPARATOR)) {
-                $delimiter = Product::PSEUDO_MULTI_LINE_SEPARATOR;
-            }
-        }
         return match ($attrParams['type']) {
             'varchar', 'text' => $this->textValidation($attrCode, $attrParams['type']),
             'decimal', 'int' => $this->numericValidation($attrCode, $attrParams['type']),
             'select', 'boolean' => $this->validateOption($attrCode, $attrParams['options'], $rowData[$attrCode]),
-            'multiselect' => $this->validateMultiselect(
-                $attrCode,
-                $attrParams['options'],
-                $rowData[$attrCode],
-                $delimiter
-            ),
+            'multiselect' => $this->validateMultiselect($attrCode, $attrParams['options'], $rowData[$attrCode]),
             'datetime' => $this->validateDateTime($rowData[$attrCode]),
             default => true,
         };
@@ -283,18 +271,16 @@ private function validateByAttributeType(string $attrCode, array $attrParams, ar
      * @param string $attrCode
      * @param array $options
      * @param array|string $rowData
-     * @param string $delimiter
      * @return bool
      */
     private function validateMultiselect(
         string $attrCode,
         array $options,
-        array|string $rowData,
-        string $delimiter = ''
+        array|string $rowData
     ): bool {
         $valid = true;
 
-        $values = $this->context->parseMultiselectValues($rowData, $delimiter);
+        $values = $this->context->parseMultiselectValues($rowData);
         foreach ($values as $value) {
             $valid = $this->validateOption($attrCode, $options, $value);
             if (!$valid) {

From e7b05df55da1aa3a76ffa60ef7760f6d2e7cec7e Mon Sep 17 00:00:00 2001
From: "Chhandak.Barua" <chhandak.barua@BLR1-LMC-N73490.local>
Date: Mon, 20 Nov 2023 17:49:36 +0530
Subject: [PATCH 03/15] ACP2E-2515: Cart Price Rule stops working properly
 after adding Bundle Product to the cart with Dynamic Price attribute disabled

---
 app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index dddaa693c896e..49031691630c6 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -132,7 +132,7 @@ public function collectRates(RateRequest $request)
                 if ($item->getHasChildren() && $item->isShipSeparately()) {
                     foreach ($item->getChildren() as $child) {
                         if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
-                            $freeShipping = is_numeric($child->getFreeShipping()) ? $child->getFreeShipping() : 0;
+                            $freeShipping = is_numeric((int)$child->getFreeShipping()) ? (int)$child->getFreeShipping() : 0;
                             $freeQty += $item->getQty() * ($child->getQty() - $freeShipping);
                         }
                     }
@@ -142,7 +142,7 @@ public function collectRates(RateRequest $request)
                 ) {
                     $freeShipping = $item->getFreeShipping() ?
                         $item->getFreeShipping() : $item->getAddress()->getFreeShipping();
-                    $freeShipping = is_numeric($freeShipping) ? $freeShipping : 0;
+                    $freeShipping = is_numeric((int)$freeShipping) ? (int)$freeShipping : 0;
                     $freeQty += $item->getQty() - $freeShipping;
                     $freePackageValue += $item->getBaseRowTotal();
                 }

From 37b686868a67890829c8b9c74edb67e0e5fea5a1 Mon Sep 17 00:00:00 2001
From: "Chhandak.Barua" <chhandak.barua@BLR1-LMC-N73490.local>
Date: Tue, 21 Nov 2023 17:51:17 +0530
Subject: [PATCH 04/15] ACP2E-2515: Cart Price Rule stops working properly
 after adding Bundle Product to the cart with Dynamic Price attribute disabled

---
 app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index 49031691630c6..80dd9f18c174a 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -142,7 +142,7 @@ public function collectRates(RateRequest $request)
                 ) {
                     $freeShipping = $item->getFreeShipping() ?
                         $item->getFreeShipping() : $item->getAddress()->getFreeShipping();
-                    $freeShipping = is_numeric((int)$freeShipping) ? (int)$freeShipping : 0;
+                    $freeShipping = is_numeric($freeShipping) ? $freeShipping : 0;
                     $freeQty += $item->getQty() - $freeShipping;
                     $freePackageValue += $item->getBaseRowTotal();
                 }

From 3ad466762322ce819ad4ff990737050eb92b2df6 Mon Sep 17 00:00:00 2001
From: "Chhandak.Barua" <chhandak.barua@BLR1-LMC-N73490.local>
Date: Tue, 21 Nov 2023 22:52:26 +0530
Subject: [PATCH 05/15] ACP2E-2515: Cart Price Rule stops working properly
 after adding Bundle Product to the cart with Dynamic Price attribute disabled

---
 .../Magento/OfflineShipping/Model/Carrier/Tablerate.php    | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index 80dd9f18c174a..092766bdedfd3 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -132,14 +132,17 @@ public function collectRates(RateRequest $request)
                 if ($item->getHasChildren() && $item->isShipSeparately()) {
                     foreach ($item->getChildren() as $child) {
                         if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
-                            $freeShipping = is_numeric((int)$child->getFreeShipping()) ? (int)$child->getFreeShipping() : 0;
+                            $freeShipping = is_numeric((int)$child->getFreeShipping())
+                                ? (int)$child->getFreeShipping()
+                                : 0;
                             $freeQty += $item->getQty() * ($child->getQty() - $freeShipping);
                         }
                     }
                 } elseif (($item->getFreeShipping() || $item->getAddress()->getFreeShipping()) &&
                     ($item->getFreeShippingMethod() == null || $item->getFreeShippingMethod() &&
                     $item->getFreeShippingMethod() == 'tablerate_bestway')
-                ) {
+                )
+                {
                     $freeShipping = $item->getFreeShipping() ?
                         $item->getFreeShipping() : $item->getAddress()->getFreeShipping();
                     $freeShipping = is_numeric($freeShipping) ? $freeShipping : 0;

From be2ce3ac27ddd8783d4338cba8b11dc0fd376e46 Mon Sep 17 00:00:00 2001
From: "Chhandak.Barua" <chhandak.barua@BLR1-LMC-N73490.local>
Date: Wed, 22 Nov 2023 11:51:19 +0530
Subject: [PATCH 06/15] ACP2E-2515: Cart Price Rule stops working properly
 after adding Bundle Product to the cart with Dynamic Price attribute disabled

---
 .../Magento/OfflineShipping/Model/Carrier/Tablerate.php     | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index 092766bdedfd3..9b6785ac31275 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -133,16 +133,14 @@ public function collectRates(RateRequest $request)
                     foreach ($item->getChildren() as $child) {
                         if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
                             $freeShipping = is_numeric((int)$child->getFreeShipping())
-                                ? (int)$child->getFreeShipping()
-                                : 0;
+                                ? (int)$child->getFreeShipping() : 0;
                             $freeQty += $item->getQty() * ($child->getQty() - $freeShipping);
                         }
                     }
                 } elseif (($item->getFreeShipping() || $item->getAddress()->getFreeShipping()) &&
                     ($item->getFreeShippingMethod() == null || $item->getFreeShippingMethod() &&
                     $item->getFreeShippingMethod() == 'tablerate_bestway')
-                )
-                {
+                ) {
                     $freeShipping = $item->getFreeShipping() ?
                         $item->getFreeShipping() : $item->getAddress()->getFreeShipping();
                     $freeShipping = is_numeric($freeShipping) ? $freeShipping : 0;

From 037c514bae8d3d79293c257c088c5f26aa516bdb Mon Sep 17 00:00:00 2001
From: "Chhandak.Barua" <chhandak.barua@BLR1-LMC-N73490.local>
Date: Thu, 23 Nov 2023 22:14:24 +0530
Subject: [PATCH 07/15] ACP2E-2515: Cart Price Rule stops working properly
 after adding Bundle Product to the cart with Dynamic Price attribute disabled

---
 .../Test/Unit/Model/Carrier/TablerateTest.php | 20 +++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php
index 7b99a8d354d9e..b2790565cf9d0 100644
--- a/app/code/Magento/OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php
+++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php
@@ -118,10 +118,11 @@ protected function setUp(): void
 
     /**
      * @param bool $freeshipping
+     * @param bool $isShipSeparately
      * @dataProvider collectRatesWithGlobalFreeShippingDataProvider
      * @return void
      */
-    public function testCollectRatesWithGlobalFreeShipping($freeshipping)
+    public function testCollectRatesWithGlobalFreeShipping($freeshipping, $isShipSeparately)
     {
         $rate = [
             'price' => 15,
@@ -177,11 +178,17 @@ public function testCollectRatesWithGlobalFreeShipping($freeshipping)
         $this->resultFactoryMock->expects($this->once())->method('create')->willReturn($result);
 
         $product->expects($this->any())->method('isVirtual')->willReturn(false);
-
         $item->expects($this->any())->method('getProduct')->willReturn($product);
-        $item->expects($this->any())->method('getFreeShipping')->willReturn(1);
         $item->expects($this->any())->method('getQty')->willReturn(1);
-
+        if ($isShipSeparately) {
+            $freeShippingReturnValue = true;
+            $item->expects($this->any())->method('getHasChildren')->willReturn(1);
+            $item->expects($this->any())->method('isShipSeparately')->willReturn(1);
+            $item->expects($this->any())->method('getChildren')->willReturn([$item]);
+        } else {
+            $freeShippingReturnValue = 1;
+        }
+        $item->expects($this->any())->method('getFreeShipping')->willReturn($freeShippingReturnValue);
         $request->expects($this->any())->method('getAllItems')->willReturn([$item]);
         $request->expects($this->any())->method('getPackageQty')->willReturn(1);
 
@@ -225,8 +232,9 @@ private function captureArg(&$captureVar)
     public function collectRatesWithGlobalFreeShippingDataProvider()
     {
         return [
-            ['freeshipping' => true],
-            ['freeshipping' => false]
+            ['freeshipping' => true, 'isShipSeparately' => false],
+            ['freeshipping' => false, 'isShipSeparately' => false],
+            ['freeshipping' => true, 'isShipSeparately' => true]
         ];
     }
 }

From 7db18d4359a498d6825d69b6d4f5f60519ca06c3 Mon Sep 17 00:00:00 2001
From: pradeep1819 <pradeep05.pro@gmail.com>
Date: Fri, 24 Nov 2023 13:39:01 +0530
Subject: [PATCH 08/15] ACP2E-2261: [Cloud] Product import fails with custom
 separator value

---
 .../Model/Import/Product.php                  | 32 ++++---------
 .../Model/Import/Product/Validator.php        |  8 +---
 .../Model/Import/Product/ValidatorTest.php    | 46 +++++--------------
 .../Test/Unit/Model/Import/ProductTest.php    |  5 +-
 .../ProductTest/ProductValidationTest.php     |  2 +-
 ...th_custom_multiselect_values_separator.csv |  2 +-
 6 files changed, 28 insertions(+), 67 deletions(-)

diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 5d65fa3cc4ea8..1493448ae3b37 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -2836,7 +2836,7 @@ private function prepareNewSkuData($sku)
      *
      * @return array
      */
-    private function _parseAdditionalAttributes(array $rowData): array
+    private function _parseAdditionalAttributes($rowData)
     {
         if (empty($rowData['additional_attributes'])) {
             return $rowData;
@@ -2846,7 +2846,7 @@ private function _parseAdditionalAttributes(array $rowData): array
                 $rowData[mb_strtolower($key)] = $value;
             }
         } else {
-            $rowData = array_merge($rowData, $this->getAdditionalAttributes($rowData));
+            $rowData = array_merge($rowData, $this->getAdditionalAttributes($rowData['additional_attributes']));
         }
         return $rowData;
     }
@@ -2860,14 +2860,14 @@ private function _parseAdditionalAttributes(array $rowData): array
      *      codeN => valueN
      * ]
      *
-     * @param array $rowData
+     * @param string $additionalAttributes Attributes data that will be parsed
      * @return array
      */
-    private function getAdditionalAttributes(array $rowData): array
+    private function getAdditionalAttributes($additionalAttributes)
     {
         return empty($this->_parameters[Import::FIELDS_ENCLOSURE])
-            ? $this->parseAttributesWithoutWrappedValues($rowData['additional_attributes'], $rowData['product_type'])
-            : $this->parseAttributesWithWrappedValues($rowData['additional_attributes']);
+            ? $this->parseAttributesWithoutWrappedValues($additionalAttributes)
+            : $this->parseAttributesWithWrappedValues($additionalAttributes);
     }
 
     /**
@@ -2881,10 +2881,9 @@ private function getAdditionalAttributes(array $rowData): array
      *
      * @param string $attributesData Attributes data that will be parsed. It keeps data in format:
      *      code=value,code2=value2...,codeN=valueN
-     * @param string $productType
      * @return array
      */
-    private function parseAttributesWithoutWrappedValues(string $attributesData, string $productType): array
+    private function parseAttributesWithoutWrappedValues($attributesData)
     {
         $attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $attributesData);
         $preparedAttributes = [];
@@ -2900,21 +2899,10 @@ private function parseAttributesWithoutWrappedValues(string $attributesData, str
             }
             list($code, $value) = explode(self::PAIR_NAME_VALUE_SEPARATOR, $attributeData, 2);
             $code = mb_strtolower($code);
-
-            $entityTypeModel = $this->retrieveProductTypeByName($productType);
-            if ($entityTypeModel) {
-                $attrParams = $entityTypeModel->retrieveAttributeFromCache($code);
-                if (!empty($attrParams) && $attrParams['type'] ==  'multiselect') {
-                    $attributeValue = $this->parseMultiselectValues($value, Product::PSEUDO_MULTI_LINE_SEPARATOR);
-                    if (count($attributeValue) > 1) {
-                        $preparedAttributes[$code] = $attributeValue;
-                    } else {
-                        $preparedAttributes[$code] = $value;
-                    }
-                }
-            } else {
-                $preparedAttributes[$code] = $value;
+            if (str_contains($value, self::PSEUDO_MULTI_LINE_SEPARATOR)) {
+                $value = $this->parseMultiselectValues($value, self::PSEUDO_MULTI_LINE_SEPARATOR);
             }
+            $preparedAttributes[$code] = $value;
         }
         return $preparedAttributes;
     }
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
index b4fae5f821ae4..f74886069d501 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
@@ -8,7 +8,6 @@
 use Magento\CatalogImportExport\Model\Import\Product;
 use Magento\Framework\Validator\AbstractValidator;
 use Magento\Catalog\Model\Product\Attribute\Backend\Sku;
-use Magento\ImportExport\Model\Import;
 
 /**
  * Product import model validator
@@ -273,11 +272,8 @@ private function validateByAttributeType(string $attrCode, array $attrParams, ar
      * @param array|string $rowData
      * @return bool
      */
-    private function validateMultiselect(
-        string $attrCode,
-        array $options,
-        array|string $rowData
-    ): bool {
+    private function validateMultiselect(string $attrCode, array $options, array|string $rowData): bool
+    {
         $valid = true;
 
         $values = $this->context->parseMultiselectValues($rowData);
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
index 0a46bf26d440c..68b75e71f1897 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
@@ -47,7 +47,7 @@ protected function setUp(): void
         $entityTypeModel->expects($this->any())->method('retrieveAttributeFromCache')->willReturn([]);
         $this->context = $this->createPartialMock(
             Product::class,
-            ['retrieveProductTypeByName', 'retrieveMessageTemplate', 'getBehavior']
+            ['retrieveProductTypeByName', 'retrieveMessageTemplate', 'getBehavior', 'getMultipleValueSeparator']
         );
         $this->context->expects($this->any())->method('retrieveProductTypeByName')->willReturn($entityTypeModel);
         $this->context->expects($this->any())->method('retrieveMessageTemplate')->willReturn('error message');
@@ -83,6 +83,7 @@ protected function setUp(): void
      */
     public function testAttributeValidation($behavior, $attrParams, $rowData, $isValid, $attrCode = 'attribute_code')
     {
+        $this->context->method('getMultipleValueSeparator')->willReturn(Product::PSEUDO_MULTI_LINE_SEPARATOR);
         $this->context->expects($this->any())->method('getBehavior')->willReturn($behavior);
         $result = $this->validator->isAttributeValid(
             $attrCode,
@@ -169,58 +170,33 @@ public function attributeValidationProvider()
             [
                 Import::BEHAVIOR_APPEND,
                 ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0, 'option 2' => 1]],
-                [
-                    'product_type' => 'any',
-                    'attribute_code' => 'Option 1|Option 2|Option 3',
-                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 1|Option 2|Option 3'
-                ],
+                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2|Option 3'],
                 false
             ],
             [
                 Import::BEHAVIOR_APPEND,
                 ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0, 'option 2' => 1]],
-                [
-                    'product_type' => 'any',
-                    'attribute_code' => 'Option 1|Option 2',
-                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 1|Option 2'
-                ],
+                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2'],
                 true
             ],
             [
                 Import::BEHAVIOR_APPEND,
-                [
-                    'is_required' => true,
-                    'type' => 'multiselect',
-                    'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']
-                ],
-                [
-                    'product_type' => 'any',
-                    'attribute_code' => 'Option 1|Option 2|Option 1',
-                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 1|Option 2|Option 1'
-                ],
+                ['is_required' => true, 'type' => 'multiselect',
+                    'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']],
+                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2|Option 1'],
                 false
             ],
             [
                 Import::BEHAVIOR_APPEND,
-                [
-                    'is_required' => true, 'type' => 'multiselect',
-                    'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']
-                ],
-                [
-                    'product_type' => 'any',
-                    'attribute_code' => 'Option 3|Option 3|Option 3|Option 1',
-                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 3|Option 3|Option 3|Option 1'
-                ],
+                ['is_required' => true, 'type' => 'multiselect',
+                    'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']],
+                ['product_type' => 'any', 'attribute_code' => 'Option 3|Option 3|Option 3|Option 1'],
                 false
             ],
             [
                 Import::BEHAVIOR_APPEND,
                 ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0]],
-                [
-                    'product_type' => 'any',
-                    'attribute_code' => 'Option 1|Option 1|Option 1|Option 1',
-                    'additional_attributes' => 'test_attribute=any,attribute_code=Option 1|Option 1|Option 1|Option 1'
-                ],
+                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 1|Option 1|Option 1'],
                 false
             ],
             [
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
index a35d76a8bee1e..758792f6610f9 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
@@ -1471,7 +1471,7 @@ public function testGetImagesFromRow($rowData, $expectedResult): void
      */
     public function testParseAttributesWithoutWrappedValuesWillReturnsLowercasedAttributeCodes(): void
     {
-        $attributesData = 'PARAM1=value1,param2=value2';
+        $attributesData = 'PARAM1=value1,param2=value2|value3';
         $preparedAttributes = $this->invokeMethod(
             $this->importProduct,
             'parseAttributesWithoutWrappedValues',
@@ -1482,7 +1482,8 @@ public function testParseAttributesWithoutWrappedValuesWillReturnsLowercasedAttr
         $this->assertEquals('value1', $preparedAttributes['param1']);
 
         $this->assertArrayHasKey('param2', $preparedAttributes);
-        $this->assertEquals('value2', $preparedAttributes['param2']);
+        $this->assertTrue(in_array('value2', $preparedAttributes['param2']));
+        $this->assertTrue(in_array('value3', $preparedAttributes['param2']));
 
         $this->assertArrayNotHasKey('PARAM1', $preparedAttributes);
     }
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductValidationTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductValidationTest.php
index b7798853f2c90..2a235ab8587c8 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductValidationTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductValidationTest.php
@@ -392,7 +392,7 @@ public function testValidateMultiselectValuesWithCustomSeparator(): void
         $params = [
             'behavior' => Import::BEHAVIOR_ADD_UPDATE,
             'entity' => 'catalog_product',
-            Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR => '###'
+            Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR => '|||'
         ];
 
         $errors = $this->_model->setParameters($params)
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_custom_multiselect_values_separator.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_custom_multiselect_values_separator.csv
index e14bd5ce2ea71..9a0549efcb860 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_custom_multiselect_values_separator.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_custom_multiselect_values_separator.csv
@@ -1,2 +1,2 @@
 sku,store_view_code,product_type,name,price,additional_attributes
-simple_ms_2,,simple,"With Multiselect 2",10,"multiselect_attribute=Option 2###Option 3"
+simple_ms_2,,simple,"With Multiselect 2",10,"multiselect_attribute=Option 2|||Option 3"

From a394ae15d3165facba08993259340da6e9e9072d Mon Sep 17 00:00:00 2001
From: pradeep1819 <pradeep05.pro@gmail.com>
Date: Fri, 24 Nov 2023 17:38:02 +0530
Subject: [PATCH 09/15] ACP2E-2261: [Cloud] Product import fails with custom
 separator value

---
 .../Magento/CatalogImportExport/Model/Import/Product.php  | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 1493448ae3b37..fb3ae4cc10068 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -2894,6 +2894,14 @@ private function parseAttributesWithoutWrappedValues($attributesData)
                 if (!$code) {
                     continue;
                 }
+                //concatenate attribute values with last used separator in case of array
+                if (is_array($preparedAttributes[$code])
+                    && str_contains($attributesData, self::PSEUDO_MULTI_LINE_SEPARATOR)) {
+                    $preparedAttributes[$code] = implode(
+                        self::PSEUDO_MULTI_LINE_SEPARATOR,
+                        $preparedAttributes[$code]
+                    );
+                }
                 $preparedAttributes[$code] .= $this->getMultipleValueSeparator() . $attributeData;
                 continue;
             }

From 5ca94916ef8280bf2134c1bc14b7b5c36b6d4b60 Mon Sep 17 00:00:00 2001
From: pradeep1819 <pradeep05.pro@gmail.com>
Date: Fri, 24 Nov 2023 19:40:42 +0530
Subject: [PATCH 10/15] ACP2E-2261: [Cloud] Product import fails with custom
 separator value

---
 app/code/Magento/CatalogImportExport/Model/Import/Product.php | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index fb3ae4cc10068..4140a7e1aa147 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -2962,6 +2962,9 @@ private function parseAttributesWithWrappedValues($attributesData)
     public function parseMultiselectValues($values, $delimiter = '')
     {
         if (empty($this->_parameters[Import::FIELDS_ENCLOSURE])) {
+            if (is_array($values)) {
+                return $values;
+            }
             if (!$delimiter) {
                 $delimiter = $this->getMultipleValueSeparator();
             }

From 66563fb3c61f8969ce42e614c607898dab92c8d5 Mon Sep 17 00:00:00 2001
From: "Chhandak.Barua" <chhandak.barua@BLR1-LMC-N73490.local>
Date: Mon, 27 Nov 2023 12:27:22 +0530
Subject: [PATCH 11/15] ACP2E-2515: Cart Price Rule stops working properly
 after adding Bundle Product to the cart with Dynamic Price attribute disabled

---
 .../OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php
index b2790565cf9d0..8814d8d45b773 100644
--- a/app/code/Magento/OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php
+++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/Carrier/TablerateTest.php
@@ -186,7 +186,7 @@ public function testCollectRatesWithGlobalFreeShipping($freeshipping, $isShipSep
             $item->expects($this->any())->method('isShipSeparately')->willReturn(1);
             $item->expects($this->any())->method('getChildren')->willReturn([$item]);
         } else {
-            $freeShippingReturnValue = 1;
+            $freeShippingReturnValue = "1";
         }
         $item->expects($this->any())->method('getFreeShipping')->willReturn($freeShippingReturnValue);
         $request->expects($this->any())->method('getAllItems')->willReturn([$item]);

From 5e9ba20225c4b167b018cb208a9fc468e59a01c8 Mon Sep 17 00:00:00 2001
From: "Chhandak.Barua" <chhandak.barua@BLR1-LMC-N73490.local>
Date: Fri, 1 Dec 2023 16:28:19 +0530
Subject: [PATCH 12/15] ACP2E-2515: Cart Price Rule stops working properly
 after adding Bundle Product to the cart with Dynamic Price attribute disabled

---
 app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index 9b6785ac31275..69af732ccc5a8 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -132,7 +132,7 @@ public function collectRates(RateRequest $request)
                 if ($item->getHasChildren() && $item->isShipSeparately()) {
                     foreach ($item->getChildren() as $child) {
                         if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
-                            $freeShipping = is_numeric((int)$child->getFreeShipping())
+                            $freeShipping = (int)$child->getFreeShipping()
                                 ? (int)$child->getFreeShipping() : 0;
                             $freeQty += $item->getQty() * ($child->getQty() - $freeShipping);
                         }

From d301755c38ebd91644a62735f04e5f05381dbb58 Mon Sep 17 00:00:00 2001
From: pradeep1819 <pradeep05.pro@gmail.com>
Date: Sun, 3 Dec 2023 20:28:06 +0530
Subject: [PATCH 13/15] ACP2E-2261: [Cloud] Product import fails with custom
 separator value

---
 .../Model/Import/Product.php                  | 43 ++++++++++---------
 .../Test/Unit/Model/Import/ProductTest.php    | 24 ++++++++---
 2 files changed, 41 insertions(+), 26 deletions(-)

diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 4140a7e1aa147..bd0bf17487e58 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -938,7 +938,7 @@ public function __construct(
         $this->_optionEntity = $data['option_entity'] ??
             $optionFactory->create(['data' => ['product_entity' => $this]]);
         $this->skuStorage = $skuStorage ?? ObjectManager::getInstance()
-            ->get(SkuStorage::class);
+                ->get(SkuStorage::class);
         $this->_initAttributeSets()
             ->_initTypeModels()
             ->_initSkus()
@@ -948,7 +948,7 @@ public function __construct(
         $this->productRepository = $productRepository ?? ObjectManager::getInstance()
                 ->get(ProductRepositoryInterface::class);
         $this->stockItemProcessor = $stockItemProcessor ?? ObjectManager::getInstance()
-            ->get(StockItemProcessorInterface::class);
+                ->get(StockItemProcessorInterface::class);
     }
 
     /**
@@ -2002,8 +2002,8 @@ private function saveProductMediaGalleryPhase(
     private function saveProductAttributesPhase(
         array $rowData,
         int $rowScope,
-        &$previousType,
-        &$prevAttributeSet,
+              &$previousType,
+              &$prevAttributeSet,
         array &$attributes
     ) : void {
         $rowSku = $rowData[self::COL_SKU];
@@ -2836,7 +2836,7 @@ private function prepareNewSkuData($sku)
      *
      * @return array
      */
-    private function _parseAdditionalAttributes($rowData)
+    private function _parseAdditionalAttributes(array $rowData): array
     {
         if (empty($rowData['additional_attributes'])) {
             return $rowData;
@@ -2846,7 +2846,7 @@ private function _parseAdditionalAttributes($rowData)
                 $rowData[mb_strtolower($key)] = $value;
             }
         } else {
-            $rowData = array_merge($rowData, $this->getAdditionalAttributes($rowData['additional_attributes']));
+            $rowData = array_merge($rowData, $this->getAdditionalAttributes($rowData));
         }
         return $rowData;
     }
@@ -2860,14 +2860,14 @@ private function _parseAdditionalAttributes($rowData)
      *      codeN => valueN
      * ]
      *
-     * @param string $additionalAttributes Attributes data that will be parsed
+     * @param array $rowData
      * @return array
      */
-    private function getAdditionalAttributes($additionalAttributes)
+    private function getAdditionalAttributes(array $rowData): array
     {
         return empty($this->_parameters[Import::FIELDS_ENCLOSURE])
-            ? $this->parseAttributesWithoutWrappedValues($additionalAttributes)
-            : $this->parseAttributesWithWrappedValues($additionalAttributes);
+            ? $this->parseAttributesWithoutWrappedValues($rowData['additional_attributes'], $rowData['product_type'])
+            : $this->parseAttributesWithWrappedValues($rowData['additional_attributes']);
     }
 
     /**
@@ -2881,9 +2881,10 @@ private function getAdditionalAttributes($additionalAttributes)
      *
      * @param string $attributesData Attributes data that will be parsed. It keeps data in format:
      *      code=value,code2=value2...,codeN=valueN
+     * @param string $productType
      * @return array
      */
-    private function parseAttributesWithoutWrappedValues($attributesData)
+    private function parseAttributesWithoutWrappedValues(string $attributesData, string $productType): array
     {
         $attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $attributesData);
         $preparedAttributes = [];
@@ -2894,21 +2895,21 @@ private function parseAttributesWithoutWrappedValues($attributesData)
                 if (!$code) {
                     continue;
                 }
-                //concatenate attribute values with last used separator in case of array
-                if (is_array($preparedAttributes[$code])
-                    && str_contains($attributesData, self::PSEUDO_MULTI_LINE_SEPARATOR)) {
-                    $preparedAttributes[$code] = implode(
-                        self::PSEUDO_MULTI_LINE_SEPARATOR,
-                        $preparedAttributes[$code]
-                    );
-                }
                 $preparedAttributes[$code] .= $this->getMultipleValueSeparator() . $attributeData;
                 continue;
             }
             list($code, $value) = explode(self::PAIR_NAME_VALUE_SEPARATOR, $attributeData, 2);
             $code = mb_strtolower($code);
-            if (str_contains($value, self::PSEUDO_MULTI_LINE_SEPARATOR)) {
-                $value = $this->parseMultiselectValues($value, self::PSEUDO_MULTI_LINE_SEPARATOR);
+
+            $entityTypeModel = $this->retrieveProductTypeByName($productType);
+            if ($entityTypeModel) {
+                $attrParams = $entityTypeModel->retrieveAttributeFromCache($code);
+                if (!empty($attrParams) && $attrParams['type'] ==  'multiselect') {
+                    $parsedValue = $this->parseMultiselectValues($value, self::PSEUDO_MULTI_LINE_SEPARATOR);
+                    if (count($parsedValue) > 1) {
+                        $value = $parsedValue;
+                    }
+                }
             }
             $preparedAttributes[$code] = $value;
         }
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
index 758792f6610f9..f43179cc58948 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
@@ -25,6 +25,7 @@
 use Magento\CatalogInventory\Api\StockConfigurationInterface;
 use Magento\CatalogInventory\Api\StockRegistryInterface;
 use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface;
+use Magento\ConfigurableImportExport\Model\Import\Product\Type\Configurable;
 use Magento\Eav\Model\Config;
 use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
 use Magento\Eav\Model\Entity\Attribute\Set;
@@ -1471,19 +1472,32 @@ public function testGetImagesFromRow($rowData, $expectedResult): void
      */
     public function testParseAttributesWithoutWrappedValuesWillReturnsLowercasedAttributeCodes(): void
     {
+        $entityTypeModel = $this->createPartialMock(
+            Configurable::class,
+            ['retrieveAttributeFromCache']
+        );
+        $entityTypeModel->expects($this->exactly(2))->method('retrieveAttributeFromCache')->willReturn([
+            'type' => 'multiselect'
+        ]);
+        $importProduct = $this->getMockBuilder(Product::class)
+            ->disableOriginalConstructor()
+            ->onlyMethods(['retrieveProductTypeByName'])
+            ->getMock();
+        $importProduct->expects($this->exactly(2))->method('retrieveProductTypeByName')->willReturn($entityTypeModel);
+
         $attributesData = 'PARAM1=value1,param2=value2|value3';
         $preparedAttributes = $this->invokeMethod(
-            $this->importProduct,
+            $importProduct,
             'parseAttributesWithoutWrappedValues',
-            [$attributesData]
+            [$attributesData, 'configurable']
         );
 
         $this->assertArrayHasKey('param1', $preparedAttributes);
         $this->assertEquals('value1', $preparedAttributes['param1']);
 
         $this->assertArrayHasKey('param2', $preparedAttributes);
-        $this->assertTrue(in_array('value2', $preparedAttributes['param2']));
-        $this->assertTrue(in_array('value3', $preparedAttributes['param2']));
+        $this->assertEquals('value2', $preparedAttributes['param2'][0]);
+        $this->assertEquals('value3', $preparedAttributes['param2'][1]);
 
         $this->assertArrayNotHasKey('PARAM1', $preparedAttributes);
     }
@@ -1685,7 +1699,7 @@ public function productCategoriesDataProvider()
                 ],
                 'catalog_category_product',
                 [
-                   [2, 5],
+                    [2, 5],
                     [
                         [
                             'product_id' => 2,

From cfc1dafa77d823b11626cc06492c9e5a904eb30b Mon Sep 17 00:00:00 2001
From: pradeep1819 <pradeep05.pro@gmail.com>
Date: Mon, 4 Dec 2023 10:23:08 +0530
Subject: [PATCH 14/15] ACP2E-2261: [Cloud] Product import fails with custom
 separator value

---
 app/code/Magento/CatalogImportExport/Model/Import/Product.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index bd0bf17487e58..39a9b60ff76d7 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -2002,8 +2002,8 @@ private function saveProductMediaGalleryPhase(
     private function saveProductAttributesPhase(
         array $rowData,
         int $rowScope,
-              &$previousType,
-              &$prevAttributeSet,
+        &$previousType,
+        &$prevAttributeSet,
         array &$attributes
     ) : void {
         $rowSku = $rowData[self::COL_SKU];

From 8bfadccfa68b736c90d03cb7f0ae447b8f6f154f Mon Sep 17 00:00:00 2001
From: "Chhandak.Barua" <chhandak.barua@BLR1-LMC-N73490.local>
Date: Wed, 6 Dec 2023 17:12:41 +0530
Subject: [PATCH 15/15] ACP2E-2515: Cart Price Rule stops working properly
 after adding Bundle Product to the cart with Dynamic Price attribute disabled

---
 app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index 69af732ccc5a8..01328e0df63a1 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -132,8 +132,7 @@ public function collectRates(RateRequest $request)
                 if ($item->getHasChildren() && $item->isShipSeparately()) {
                     foreach ($item->getChildren() as $child) {
                         if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
-                            $freeShipping = (int)$child->getFreeShipping()
-                                ? (int)$child->getFreeShipping() : 0;
+                            $freeShipping = (int)$child->getFreeShipping();
                             $freeQty += $item->getQty() * ($child->getQty() - $freeShipping);
                         }
                     }