Skip to content

Commit 236b467

Browse files
author
Roman Lytvynenko
committed
MAGETWO-99500: Configurable options attribute position is not saved correctly via API
1 parent dffe94d commit 236b467

File tree

4 files changed

+327
-34
lines changed

4 files changed

+327
-34
lines changed

app/code/Magento/ConfigurableProduct/Model/LinkManagement.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public function addChild($sku, $childSku)
132132
throw new StateException(__("The parent product doesn't have configurable product options."));
133133
}
134134

135-
$attributeIds = [];
135+
$attributeData = [];
136136
foreach ($configurableProductOptions as $configurableProductOption) {
137137
$attributeCode = $configurableProductOption->getProductAttribute()->getAttributeCode();
138138
if (!$child->getData($attributeCode)) {
@@ -143,9 +143,11 @@ public function addChild($sku, $childSku)
143143
)
144144
);
145145
}
146-
$attributeIds[] = $configurableProductOption->getAttributeId();
146+
$attributeData[$configurableProductOption->getAttributeId()] = [
147+
'position' => $configurableProductOption->getPosition()
148+
];
147149
}
148-
$configurableOptionData = $this->getConfigurableAttributesData($attributeIds);
150+
$configurableOptionData = $this->getConfigurableAttributesData($attributeData);
149151

150152
/** @var \Magento\ConfigurableProduct\Helper\Product\Options\Factory $optionFactory */
151153
$optionFactory = $this->getOptionsFactory();
@@ -211,16 +213,16 @@ private function getOptionsFactory()
211213
/**
212214
* Get Configurable Attribute Data
213215
*
214-
* @param int[] $attributeIds
216+
* @param int[] $attributeData
215217
* @return array
216218
*/
217-
private function getConfigurableAttributesData($attributeIds)
219+
private function getConfigurableAttributesData($attributeData)
218220
{
219221
$configurableAttributesData = [];
220222
$attributeValues = [];
221223
$attributes = $this->attributeFactory->create()
222224
->getCollection()
223-
->addFieldToFilter('attribute_id', $attributeIds)
225+
->addFieldToFilter('attribute_id', array_keys($attributeData))
224226
->getItems();
225227
foreach ($attributes as $attribute) {
226228
foreach ($attribute->getOptions() as $option) {
@@ -237,6 +239,7 @@ private function getConfigurableAttributesData($attributeIds)
237239
'attribute_id' => $attribute->getId(),
238240
'code' => $attribute->getAttributeCode(),
239241
'label' => $attribute->getStoreLabel(),
242+
'position' => $attributeData[$attribute->getId()]['position'],
240243
'values' => $attributeValues,
241244
];
242245
}

dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/LinkManagementTest.php

Lines changed: 176 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66
*/
77
namespace Magento\ConfigurableProduct\Api;
88

9+
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
910
use Magento\Eav\Model\AttributeRepository;
11+
use Magento\Eav\Model\Entity\Attribute\Option;
12+
use Magento\Framework\Webapi\Rest\Request;
13+
use Magento\TestFramework\TestCase\WebapiAbstract;
1014

11-
class LinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract
15+
class LinkManagementTest extends WebapiAbstract
1216
{
1317
const SERVICE_NAME = 'configurableProductLinkManagementV1';
18+
const OPTION_SERVICE_NAME = 'configurableProductOptionRepositoryV1';
1419
const SERVICE_VERSION = 'V1';
1520
const RESOURCE_PATH = '/V1/configurable-products';
1621

@@ -85,9 +90,27 @@ public function testAddChildFullRestCreation()
8590

8691
$this->createConfigurableProduct($productSku);
8792
$attribute = $this->attributeRepository->get('catalog_product', 'test_configurable');
88-
$attributeValue = $attribute->getOptions()[1]->getValue();
89-
$this->addOptionToConfigurableProduct($productSku, $attribute->getAttributeId(), $attributeValue);
90-
$this->createSimpleProduct($childSku, $attributeValue);
93+
94+
$this->addOptionToConfigurableProduct(
95+
$productSku,
96+
$attribute->getAttributeId(),
97+
[
98+
[
99+
'value_index' => $attribute->getOptions()[1]->getValue()
100+
]
101+
]
102+
);
103+
104+
$this->createSimpleProduct(
105+
$childSku,
106+
[
107+
[
108+
'attribute_code' => 'test_configurable',
109+
'value' => $attribute->getOptions()[1]->getValue()
110+
]
111+
]
112+
);
113+
91114
$res = $this->addChild($productSku, $childSku);
92115
$this->assertTrue($res);
93116

@@ -103,38 +126,167 @@ public function testAddChildFullRestCreation()
103126
$this->assertTrue($added);
104127

105128
// clean up products
129+
130+
$this->deleteProduct($productSku);
131+
$this->deleteProduct($childSku);
132+
}
133+
134+
/**
135+
* Test if configurable option attribute positions are being preserved after simple products were assigned to a
136+
* configurable product.
137+
*
138+
* @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_attributes_for_position_test.php
139+
*/
140+
public function testConfigurableOptionPositionPreservation()
141+
{
142+
$productSku = 'configurable-product-sku';
143+
$childProductSkus = [
144+
'simple-product-sku-1',
145+
'simple-product-sku-2'
146+
];
147+
$attributesToAdd = [
148+
'custom_attr_1',
149+
'custom_attr_2',
150+
];
151+
152+
$this->createConfigurableProduct($productSku);
153+
154+
$position = 0;
155+
$attributeOptions = [];
156+
foreach ($attributesToAdd as $attributeToAdd) {
157+
/** @var Attribute $attribute */
158+
$attribute = $this->attributeRepository->get('catalog_product', $attributeToAdd);
159+
160+
/** @var Option $options[] */
161+
$options = $attribute->getOptions();
162+
array_shift($options);
163+
164+
$attributeOptions[$attributeToAdd] = $options;
165+
166+
$valueIndexesData = [];
167+
foreach ($options as $option) {
168+
$valueIndexesData []['value_index']= $option->getValue();
169+
}
170+
$this->addOptionToConfigurableProduct(
171+
$productSku,
172+
$attribute->getAttributeId(),
173+
$valueIndexesData,
174+
$position
175+
);
176+
$position++;
177+
}
178+
179+
$this->assertArrayHasKey($attributesToAdd[0], $attributeOptions);
180+
$this->assertArrayHasKey($attributesToAdd[1], $attributeOptions);
181+
$this->assertCount(4, $attributeOptions[$attributesToAdd[0]]);
182+
$this->assertCount(4, $attributeOptions[$attributesToAdd[1]]);
183+
184+
$attributesBeforeAssign = $this->getConfigurableAttribute($productSku);
185+
186+
$simpleProdsAttributeData = [];
187+
foreach ($attributeOptions as $attributeCode => $options) {
188+
$simpleProdsAttributeData [0][] = [
189+
'attribute_code' => $attributeCode,
190+
'value' => $options[0]->getValue(),
191+
];
192+
$simpleProdsAttributeData [0][] = [
193+
'attribute_code' => $attributeCode,
194+
'value' => $options[1]->getValue(),
195+
];
196+
$simpleProdsAttributeData [1][] = [
197+
'attribute_code' => $attributeCode,
198+
'value' => $options[2]->getValue(),
199+
];
200+
$simpleProdsAttributeData [1][] = [
201+
'attribute_code' => $attributeCode,
202+
'value' => $options[3]->getValue(),
203+
];
204+
}
205+
206+
foreach ($childProductSkus as $childNum => $childSku) {
207+
$this->createSimpleProduct($childSku, $simpleProdsAttributeData[$childNum]);
208+
$res = $this->addChild($productSku, $childSku);
209+
$this->assertTrue($res);
210+
}
211+
212+
$childProductsDiff = array_diff(
213+
$childProductSkus,
214+
array_column(
215+
$this->getChildren($productSku),
216+
'sku'
217+
)
218+
);
219+
$this->assertCount(0, $childProductsDiff, 'Added child product count mismatch expected result');
220+
221+
$attributesAfterAssign = $this->getConfigurableAttribute($productSku);
222+
223+
$this->assertEquals(
224+
$attributesBeforeAssign[0]['position'],
225+
$attributesAfterAssign[0]['position'],
226+
'Product 1 attribute option position mismatch'
227+
);
228+
$this->assertEquals(
229+
$attributesBeforeAssign[1]['position'],
230+
$attributesAfterAssign[1]['position'],
231+
'Product 2 attribute option position mismatch'
232+
);
233+
234+
foreach ($childProductSkus as $childSku) {
235+
$this->deleteProduct($childSku);
236+
}
237+
$this->deleteProduct($productSku);
238+
}
239+
240+
/**
241+
* Delete product by SKU
242+
*
243+
* @param string $sku
244+
* @return bool
245+
*/
246+
private function deleteProduct(string $sku): bool
247+
{
106248
$serviceInfo = [
107249
'rest' => [
108-
'resourcePath' => '/V1/products/' . $productSku,
109-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE
250+
'resourcePath' => '/V1/products/' . $sku,
251+
'httpMethod' => Request::HTTP_METHOD_DELETE
110252
],
111253
'soap' => [
112254
'service' => 'catalogProductRepositoryV1',
113255
'serviceVersion' => self::SERVICE_VERSION,
114256
'operation' => 'catalogProductRepositoryV1DeleteById',
115257
],
116258
];
117-
$this->_webApiCall($serviceInfo, ['sku' => $productSku]);
259+
return $this->_webApiCall($serviceInfo, ['sku' => $sku]);
260+
}
261+
262+
/**
263+
* Get configurable product attributes
264+
*
265+
* @param string $productSku
266+
* @return array
267+
*/
268+
protected function getConfigurableAttribute(string $productSku): array
269+
{
118270
$serviceInfo = [
119271
'rest' => [
120-
'resourcePath' => '/V1/products/' . $childSku,
121-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE
272+
'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/options/all',
273+
'httpMethod' => Request::HTTP_METHOD_GET
122274
],
123275
'soap' => [
124-
'service' => 'catalogProductRepositoryV1',
276+
'service' => self::OPTION_SERVICE_NAME,
125277
'serviceVersion' => self::SERVICE_VERSION,
126-
'operation' => 'catalogProductRepositoryV1DeleteById',
127-
],
278+
'operation' => self::OPTION_SERVICE_NAME . 'GetList'
279+
]
128280
];
129-
$this->_webApiCall($serviceInfo, ['sku' => $childSku]);
281+
return $this->_webApiCall($serviceInfo, ['sku' => $productSku]);
130282
}
131283

132284
private function addChild($productSku, $childSku)
133285
{
134286
$serviceInfo = [
135287
'rest' => [
136288
'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/child',
137-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
289+
'httpMethod' => Request::HTTP_METHOD_POST
138290
],
139291
'soap' => [
140292
'service' => self::SERVICE_NAME,
@@ -159,7 +311,7 @@ protected function createConfigurableProduct($productSku)
159311
$serviceInfo = [
160312
'rest' => [
161313
'resourcePath' => '/V1/products',
162-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
314+
'httpMethod' => Request::HTTP_METHOD_POST
163315
],
164316
'soap' => [
165317
'service' => 'catalogProductRepositoryV1',
@@ -170,24 +322,22 @@ protected function createConfigurableProduct($productSku)
170322
return $this->_webApiCall($serviceInfo, $requestData);
171323
}
172324

173-
protected function addOptionToConfigurableProduct($productSku, $attributeId, $attributeValue)
325+
protected function addOptionToConfigurableProduct($productSku, $attributeId, $attributeValues, $position = 0)
174326
{
175327
$requestData = [
176328
'sku' => $productSku,
177329
'option' => [
178330
'attribute_id' => $attributeId,
179331
'label' => 'test_configurable',
180-
'position' => 0,
332+
'position' => $position,
181333
'is_use_default' => true,
182-
'values' => [
183-
['value_index' => $attributeValue],
184-
]
334+
'values' => $attributeValues
185335
]
186336
];
187337
$serviceInfo = [
188338
'rest' => [
189339
'resourcePath' => '/V1/configurable-products/'. $productSku .'/options',
190-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
340+
'httpMethod' => Request::HTTP_METHOD_POST,
191341
],
192342
'soap' => [
193343
'service' => 'configurableProductOptionRepositoryV1',
@@ -198,7 +348,7 @@ protected function addOptionToConfigurableProduct($productSku, $attributeId, $at
198348
return $this->_webApiCall($serviceInfo, $requestData);
199349
}
200350

201-
protected function createSimpleProduct($sku, $attributeValue)
351+
protected function createSimpleProduct($sku, $customAttributes)
202352
{
203353
$requestData = [
204354
'product' => [
@@ -209,15 +359,13 @@ protected function createSimpleProduct($sku, $attributeValue)
209359
'price' => 3.62,
210360
'status' => 1,
211361
'visibility' => 4,
212-
'custom_attributes' => [
213-
['attribute_code' => 'test_configurable', 'value' => $attributeValue],
214-
]
362+
'custom_attributes' => $customAttributes
215363
]
216364
];
217365
$serviceInfo = [
218366
'rest' => [
219367
'resourcePath' => '/V1/products',
220-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
368+
'httpMethod' => Request::HTTP_METHOD_POST,
221369
],
222370
'soap' => [
223371
'service' => 'catalogProductRepositoryV1',
@@ -244,7 +392,7 @@ protected function removeChild($productSku, $childSku)
244392
$serviceInfo = [
245393
'rest' => [
246394
'resourcePath' => sprintf($resourcePath, $productSku, $childSku),
247-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE
395+
'httpMethod' => Request::HTTP_METHOD_DELETE
248396
],
249397
'soap' => [
250398
'service' => self::SERVICE_NAME,
@@ -265,7 +413,7 @@ protected function getChildren($productSku)
265413
$serviceInfo = [
266414
'rest' => [
267415
'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/children',
268-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET
416+
'httpMethod' => Request::HTTP_METHOD_GET
269417
],
270418
'soap' => [
271419
'service' => self::SERVICE_NAME,

0 commit comments

Comments
 (0)