Skip to content

Commit 0dc9315

Browse files
committed
Merge branch 'MAGETWO-99491' of https://github.com/magento-tango/magento2ce into PR_2019_06_18
2 parents 01a441d + ca8cdde commit 0dc9315

File tree

5 files changed

+330
-35
lines changed

5 files changed

+330
-35
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,17 @@ public function addChild($sku, $childSku)
131131
throw new StateException(__('Parent product does not have configurable product options'));
132132
}
133133

134-
$attributeIds = [];
134+
$attributeData = [];
135135
foreach ($configurableProductOptions as $configurableProductOption) {
136136
$attributeCode = $configurableProductOption->getProductAttribute()->getAttributeCode();
137137
if (!$child->getData($attributeCode)) {
138138
throw new StateException(__('Child product does not have attribute value %1', $attributeCode));
139139
}
140-
$attributeIds[] = $configurableProductOption->getAttributeId();
140+
$attributeData[$configurableProductOption->getAttributeId()] = [
141+
'position' => $configurableProductOption->getPosition()
142+
];
141143
}
142-
$configurableOptionData = $this->getConfigurableAttributesData($attributeIds);
144+
$configurableOptionData = $this->getConfigurableAttributesData($attributeData);
143145

144146
/** @var \Magento\ConfigurableProduct\Helper\Product\Options\Factory $optionFactory */
145147
$optionFactory = $this->getOptionsFactory();
@@ -203,16 +205,16 @@ private function getOptionsFactory()
203205
/**
204206
* Get Configurable Attribute Data
205207
*
206-
* @param int[] $attributeIds
208+
* @param int[] $attributeData
207209
* @return array
208210
*/
209-
private function getConfigurableAttributesData($attributeIds)
211+
private function getConfigurableAttributesData($attributeData)
210212
{
211213
$configurableAttributesData = [];
212214
$attributeValues = [];
213215
$attributes = $this->attributeFactory->create()
214216
->getCollection()
215-
->addFieldToFilter('attribute_id', $attributeIds)
217+
->addFieldToFilter('attribute_id', array_keys($attributeData))
216218
->getItems();
217219
foreach ($attributes as $attribute) {
218220
foreach ($attribute->getOptions() as $option) {
@@ -229,6 +231,7 @@ private function getConfigurableAttributesData($attributeIds)
229231
'attribute_id' => $attribute->getId(),
230232
'code' => $attribute->getAttributeCode(),
231233
'label' => $attribute->getStoreLabel(),
234+
'position' => $attributeData[$attribute->getId()]['position'],
232235
'values' => $attributeValues,
233236
];
234237
}

app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public function testAddChild()
158158
->getMock();
159159
$optionMock = $this->getMockBuilder(\Magento\ConfigurableProduct\Api\Data\Option::class)
160160
->disableOriginalConstructor()
161-
->setMethods(['getProductAttribute', 'getAttributeId'])
161+
->setMethods(['getProductAttribute', 'getPosition', 'getAttributeId'])
162162
->getMock();
163163
$productAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
164164
->disableOriginalConstructor()
@@ -216,13 +216,15 @@ public function testAddChild()
216216
$productAttributeMock->expects($this->any())->method('getAttributeCode')->willReturn('color');
217217
$simple->expects($this->any())->method('getData')->willReturn('color');
218218
$optionMock->expects($this->any())->method('getAttributeId')->willReturn('1');
219+
$optionMock->expects($this->any())->method('getPosition')->willReturn('0');
219220

220221
$optionsFactoryMock->expects($this->any())->method('create')->willReturn([$optionMock]);
221222
$attributeFactoryMock->expects($this->any())->method('create')->willReturn($attributeMock);
222223
$attributeMock->expects($this->any())->method('getCollection')->willReturn($attributeCollectionMock);
223224
$attributeCollectionMock->expects($this->any())->method('addFieldToFilter')->willReturnSelf();
224225
$attributeCollectionMock->expects($this->any())->method('getItems')->willReturn([$attributeMock]);
225226

227+
$attributeMock->expects($this->any())->method('getId')->willReturn(1);
226228
$attributeMock->expects($this->any())->method('getOptions')->willReturn([$attributeOptionMock]);
227229

228230
$extensionAttributesMock->expects($this->any())->method('setConfigurableProductOptions');

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

@@ -88,9 +93,27 @@ public function testAddChildFullRestCreation()
8893

8994
$this->createConfigurableProduct($productSku);
9095
$attribute = $this->attributeRepository->get('catalog_product', 'test_configurable');
91-
$attributeValue = $attribute->getOptions()[1]->getValue();
92-
$this->addOptionToConfigurableProduct($productSku, $attribute->getAttributeId(), $attributeValue);
93-
$this->createSimpleProduct($childSku, $attributeValue);
96+
97+
$this->addOptionToConfigurableProduct(
98+
$productSku,
99+
$attribute->getAttributeId(),
100+
[
101+
[
102+
'value_index' => $attribute->getOptions()[1]->getValue()
103+
]
104+
]
105+
);
106+
107+
$this->createSimpleProduct(
108+
$childSku,
109+
[
110+
[
111+
'attribute_code' => 'test_configurable',
112+
'value' => $attribute->getOptions()[1]->getValue()
113+
]
114+
]
115+
);
116+
94117
$res = $this->addChild($productSku, $childSku);
95118
$this->assertTrue($res);
96119

@@ -106,38 +129,167 @@ public function testAddChildFullRestCreation()
106129
$this->assertTrue($added);
107130

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

135287
private function addChild($productSku, $childSku)
136288
{
137289
$serviceInfo = [
138290
'rest' => [
139291
'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/child',
140-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
292+
'httpMethod' => Request::HTTP_METHOD_POST
141293
],
142294
'soap' => [
143295
'service' => self::SERVICE_NAME,
@@ -162,7 +314,7 @@ protected function createConfigurableProduct($productSku)
162314
$serviceInfo = [
163315
'rest' => [
164316
'resourcePath' => '/V1/products',
165-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST
317+
'httpMethod' => Request::HTTP_METHOD_POST
166318
],
167319
'soap' => [
168320
'service' => 'catalogProductRepositoryV1',
@@ -173,24 +325,22 @@ protected function createConfigurableProduct($productSku)
173325
return $this->_webApiCall($serviceInfo, $requestData);
174326
}
175327

176-
protected function addOptionToConfigurableProduct($productSku, $attributeId, $attributeValue)
328+
protected function addOptionToConfigurableProduct($productSku, $attributeId, $attributeValues, $position = 0)
177329
{
178330
$requestData = [
179331
'sku' => $productSku,
180332
'option' => [
181333
'attribute_id' => $attributeId,
182334
'label' => 'test_configurable',
183-
'position' => 0,
335+
'position' => $position,
184336
'is_use_default' => true,
185-
'values' => [
186-
['value_index' => $attributeValue],
187-
]
337+
'values' => $attributeValues
188338
]
189339
];
190340
$serviceInfo = [
191341
'rest' => [
192342
'resourcePath' => '/V1/configurable-products/'. $productSku .'/options',
193-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
343+
'httpMethod' => Request::HTTP_METHOD_POST,
194344
],
195345
'soap' => [
196346
'service' => 'configurableProductOptionRepositoryV1',
@@ -201,7 +351,7 @@ protected function addOptionToConfigurableProduct($productSku, $attributeId, $at
201351
return $this->_webApiCall($serviceInfo, $requestData);
202352
}
203353

204-
protected function createSimpleProduct($sku, $attributeValue)
354+
protected function createSimpleProduct($sku, $customAttributes)
205355
{
206356
$requestData = [
207357
'product' => [
@@ -212,15 +362,13 @@ protected function createSimpleProduct($sku, $attributeValue)
212362
'price' => 3.62,
213363
'status' => 1,
214364
'visibility' => 4,
215-
'custom_attributes' => [
216-
['attribute_code' => 'test_configurable', 'value' => $attributeValue],
217-
]
365+
'custom_attributes' => $customAttributes
218366
]
219367
];
220368
$serviceInfo = [
221369
'rest' => [
222370
'resourcePath' => '/V1/products',
223-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
371+
'httpMethod' => Request::HTTP_METHOD_POST,
224372
],
225373
'soap' => [
226374
'service' => 'catalogProductRepositoryV1',
@@ -247,7 +395,7 @@ protected function removeChild($productSku, $childSku)
247395
$serviceInfo = [
248396
'rest' => [
249397
'resourcePath' => sprintf($resourcePath, $productSku, $childSku),
250-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE
398+
'httpMethod' => Request::HTTP_METHOD_DELETE
251399
],
252400
'soap' => [
253401
'service' => self::SERVICE_NAME,
@@ -268,7 +416,7 @@ protected function getChildren($productSku)
268416
$serviceInfo = [
269417
'rest' => [
270418
'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/children',
271-
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET
419+
'httpMethod' => Request::HTTP_METHOD_GET
272420
],
273421
'soap' => [
274422
'service' => self::SERVICE_NAME,

0 commit comments

Comments
 (0)