Commit 7a46831c authored by Damian Kryger's avatar Damian Kryger Committed by Piotr Borek
Browse files

#784 Mapping in Family2Family - job processing

parent b19bb0fd
......@@ -104,4 +104,9 @@ class RuleAttributeProvider
return $attributes;
}
public function getAttributeByCode(string $code): ?AttributeInterface
{
return $this->attributeRepository->findOneBy(['code' => $code]);
}
}
\ No newline at end of file
......@@ -19,6 +19,7 @@ use Akeneo\Pim\Enrichment\Component\Product\Query\ProductQueryBuilderFactoryInte
use Akeneo\Pim\Structure\Component\Model\FamilyInterface;
use Akeneo\Tool\Component\Batch\Model\StepExecution;
use Akeneo\Tool\Component\StorageUtils\Saver\SaverInterface;
use PcmtRulesBundle\Value\AttributeMapping;
use PcmtSharedBundle\Connector\Job\InvalidItems\SimpleInvalidItem;
use Ramsey\Uuid\Uuid;
......@@ -77,7 +78,8 @@ class RuleProcessor
$this->productsToSave = [];
$this->productModelsToSave = [];
$attributes = $this->ruleAttributeProvider->getAllForFamilies($sourceFamily, $destinationFamily);
$mappings = $this->getMappings($sourceFamily, $destinationFamily, $stepExecution);
$keyAttributeCode = $stepExecution->getJobParameters()->get('keyAttribute');
$keyValue = $sourceProduct->getValue($keyAttributeCode);
......@@ -104,11 +106,11 @@ class RuleProcessor
$destinationProducts = $pqb->execute();
foreach ($destinationProducts as $destinationProduct) {
$this->copy($sourceProduct, $destinationProduct, $attributes, $stepExecution);
$this->copy($sourceProduct, $destinationProduct, $mappings, $stepExecution);
}
if (0 === count($destinationProducts) && 0 === count($destinationFamily->getFamilyVariants())) {
$this->createNewDestinationProduct($sourceProduct, $destinationFamily, $attributes, $stepExecution);
$this->createNewDestinationProduct($sourceProduct, $destinationFamily, $mappings, $stepExecution);
foreach ($this->productsToSave as $product) {
$this->productSaver->save($product);
......@@ -136,21 +138,21 @@ class RuleProcessor
private function copy(
EntityWithValuesInterface $sourceProduct,
EntityWithValuesInterface $destinationProduct,
array $attributes,
array $mappings,
StepExecution $stepExecution
): void {
try {
$result = $this->ruleProcessorCopier->copy($sourceProduct, $destinationProduct, $attributes);
$result = $this->ruleProcessorCopier->copy($sourceProduct, $destinationProduct, $mappings);
if ($result) {
$this->addEntityToSave($destinationProduct);
}
if ($destinationProduct instanceof ProductModelInterface) {
foreach ($destinationProduct->getProductModels() as $productModel) {
$this->copy($sourceProduct, $productModel, $attributes, $stepExecution);
$this->copy($sourceProduct, $productModel, $mappings, $stepExecution);
}
foreach ($destinationProduct->getProducts() as $product) {
$this->copy($sourceProduct, $product, $attributes, $stepExecution);
$this->copy($sourceProduct, $product, $mappings, $stepExecution);
}
}
} catch (\Throwable $e) {
......@@ -179,7 +181,7 @@ class RuleProcessor
}
}
private function createNewDestinationProduct(EntityWithValuesInterface $sourceEntity, FamilyInterface $destinationFamily, array $attributes, StepExecution $stepExecution): void
private function createNewDestinationProduct(EntityWithValuesInterface $sourceEntity, FamilyInterface $destinationFamily, array $mappings, StepExecution $stepExecution): void
{
$destinationProduct = $this->productBuilder->createProduct(
Uuid::uuid4()->toString(),
......@@ -188,6 +190,29 @@ class RuleProcessor
$this->addEntityToSave($destinationProduct);
$this->copy($sourceEntity, $destinationProduct, $attributes, $stepExecution);
$this->copy($sourceEntity, $destinationProduct, $mappings, $stepExecution);
}
private function getMappings(FamilyInterface $sourceFamily, FamilyInterface $destinationFamily, StepExecution $stepExecution): array
{
$mappings = [];
foreach ($this->ruleAttributeProvider->getAllForFamilies($sourceFamily, $destinationFamily) as $attribute) {
$mappings[] = new AttributeMapping($attribute, $attribute);
}
foreach ($stepExecution->getJobParameters()->get('attributeMapping') as $mapping) {
$sourceAttribute = $this->ruleAttributeProvider->getAttributeByCode($mapping['sourceValue']);
$destinationAttribute = $this->ruleAttributeProvider->getAttributeByCode($mapping['destinationValue']);
if (null !== $sourceAttribute && null !== $destinationAttribute) {
$mappings[] = new AttributeMapping(
$sourceAttribute,
$destinationAttribute
);
}
}
return $mappings;
}
}
......@@ -18,6 +18,7 @@ use Akeneo\Pim\Enrichment\Component\Product\ProductModel\Filter\ProductAttribute
use Akeneo\Pim\Enrichment\Component\Product\ProductModel\Filter\ProductModelAttributeFilter;
use Akeneo\Pim\Structure\Component\Model\AttributeInterface;
use Akeneo\Tool\Component\StorageUtils\Updater\PropertyCopierInterface;
use PcmtRulesBundle\Value\AttributeMapping;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class RuleProcessorCopier
......@@ -59,12 +60,12 @@ class RuleProcessorCopier
public function copy(
EntityWithValuesInterface $sourceProduct,
EntityWithValuesInterface $destinationProduct,
array $attributes
array $mappings
): bool {
$productData = $this->normalizer->normalize($destinationProduct, 'standard');
$attributeCodes = array_map(function (AttributeInterface $attribute) {
return $attribute->getCode();
}, $attributes);
$attributeCodes = array_map(function (AttributeMapping $attributeMapping) {
return $attributeMapping->getDestinationAttribute()->getCode();
}, $mappings);
$productData['values'] = [];
foreach ($attributeCodes as $code) {
......@@ -80,9 +81,9 @@ class RuleProcessorCopier
if (0 === count($productData['values'])) {
return false;
}
foreach ($attributes as $attribute) {
if (isset($productData['values'][$attribute->getCode()])) {
$this->copyOneAttribute($sourceProduct, $destinationProduct, $attribute);
foreach ($mappings as $mapping) {
if (isset($productData['values'][$mapping->getDestinationAttribute()->getCode()])) {
$this->copyOneAttribute($sourceProduct, $destinationProduct, $mapping->getSourceAttribute(), $mapping->getDestinationAttribute());
}
}
......@@ -92,10 +93,11 @@ class RuleProcessorCopier
private function copyOneAttribute(
EntityWithValuesInterface $sourceProduct,
EntityWithValuesInterface $destinationProduct,
AttributeInterface $attribute
AttributeInterface $sourceAttribute,
AttributeInterface $destinationAttribute
): void {
$scopes = $attribute->isScopable() ? $this->channelRepository->getChannelCodes() : null;
$locales = $attribute->isLocalizable() ? $this->localeRepository->getActivatedLocaleCodes() : null;
$scopes = $sourceAttribute->isScopable() ? $this->channelRepository->getChannelCodes() : null;
$locales = $sourceAttribute->isLocalizable() ? $this->localeRepository->getActivatedLocaleCodes() : null;
$scopes = $scopes ?? [null];
$locales = $locales ?? [null];
......@@ -111,8 +113,8 @@ class RuleProcessorCopier
$this->propertyCopier->copyData(
$sourceProduct,
$destinationProduct,
$attribute->getCode(),
$attribute->getCode(),
$sourceAttribute->getCode(),
$destinationAttribute->getCode(),
$options
);
}
......
......@@ -150,6 +150,24 @@ class RuleAttributeProviderTest extends TestCase
$this->assertCount($expectedCount, $resultAttributes);
}
public function testGetAttributeByCode(): void
{
$code = 'CODE';
$attribute = (new AttributeBuilder())->withCode($code)->build();
$this->attributeRepositoryMock
->expects($this->once())
->method('findOneBy')
->with(['code' => $code])
->willReturn($attribute);
$provider = $this->getRuleAttributeProviderInstance();
$result = $provider->getAttributeByCode($code);
$this->assertEquals($attribute->getCode(), $result->getCode());
}
private function getRuleAttributeProviderInstance(): RuleAttributeProvider
{
return new RuleAttributeProvider($this->attributeRepositoryMock);
......
......@@ -19,6 +19,7 @@ use Akeneo\Pim\Enrichment\Component\Product\Value\ScalarValue;
use Akeneo\Tool\Component\StorageUtils\Updater\PropertyCopierInterface;
use PcmtRulesBundle\Service\RuleProcessorCopier;
use PcmtRulesBundle\Tests\TestDataBuilder\AttributeBuilder;
use PcmtRulesBundle\Tests\TestDataBuilder\AttributeMappingBuilder;
use PcmtRulesBundle\Tests\TestDataBuilder\ProductBuilder;
use PcmtRulesBundle\Tests\TestDataBuilder\ProductModelBuilder;
use PHPUnit\Framework\MockObject\MockObject;
......@@ -59,6 +60,11 @@ class RuleProcessorCopierTest extends TestCase
{
$attribute1 = (new AttributeBuilder())->withCode('attributeCode')->build();
$mapping = (new AttributeMappingBuilder())
->withSourceAttribute($attribute1)
->withDestinationAttribute($attribute1)
->build();
$value = ScalarValue::value($attribute1->getCode(), 'xxx');
$product1 = (new ProductBuilder())->addValue($value)->build();
......@@ -67,15 +73,15 @@ class RuleProcessorCopierTest extends TestCase
$productModel1 = (new ProductModelBuilder())->build();
return [
[$product1, $product2, [$attribute1]],
[$product1, $productModel1, [$attribute1]],
[$product1, $product2, [$mapping]],
[$product1, $productModel1, [$mapping]],
];
}
/**
* @dataProvider dataCopy
*/
public function testCopy(EntityWithValuesInterface $sourceProduct, EntityWithValuesInterface $destinationProduct, array $attributes): void
public function testCopy(EntityWithValuesInterface $sourceProduct, EntityWithValuesInterface $destinationProduct, array $mappings): void
{
$this->productAttributeFilterMock->method('filter')->willReturn([
'values' => [
......@@ -90,7 +96,7 @@ class RuleProcessorCopierTest extends TestCase
$this->propertyCopierMock->expects($this->exactly(1))->method('copyData');
$processor = $this->getRuleProcessorCopierInstance();
$result = $processor->copy($sourceProduct, $destinationProduct, $attributes);
$result = $processor->copy($sourceProduct, $destinationProduct, $mappings);
$this->assertTrue($result);
}
......
......@@ -103,24 +103,59 @@ class RuleProcessorTest extends TestCase
$sourceFamily = (new FamilyBuilder())->withCode('SOURCE')->build();
$destinationFamily = (new FamilyBuilder())->withCode('DESTINATION')->build();
$mapping = [
[
'sourceValue' => 'SOURCE_ATTRIBUTE',
'destinationValue' => 'DESTINATION_ATTRIBUTE',
],
];
return [
[$sourceFamily, $destinationFamily, $product, $destinationProducts, $attributes, 2],
[$sourceFamily, $destinationFamily, $product, $destinationProductModels, $attributes, 3],
[$sourceFamily, $destinationFamily, $product, $destinationProducts, $attributes, [], 2],
[$sourceFamily, $destinationFamily, $product, $destinationProductModels, $attributes, [], 3],
[$sourceFamily, $destinationFamily, $product, $destinationProductModels, $attributes, $mapping, 3],
];
}
/**
* @dataProvider dataProcess
*/
public function testProcess(FamilyInterface $sourceFamily, FamilyInterface $destinationFamily, ProductInterface $sourceProduct, array $destinationProducts, array $attributes, int $expectedCalls): void
public function testProcess(FamilyInterface $sourceFamily, FamilyInterface $destinationFamily, ProductInterface $sourceProduct, array $destinationProducts, array $attributes, array $attributeMapping, int $expectedCalls): void
{
$this->productBuilderMock->method('createProduct')->willReturn(
(new ProductBuilder())->withId(2332)->build()
);
$this->jobParametersMock
->expects($this->at(0))
->method('get')
->with('attributeMapping')
->willReturn($attributeMapping);
$this->jobParametersMock
->expects($this->at(1))
->method('get')
->with('keyAttribute')
->willReturn('test');
$this->ruleAttributeProviderMock->expects($this->once())->method('getAllForFamilies')->willReturn($attributes);
$this->ruleAttributeProviderMock->expects($this->at(0))->method('getAllForFamilies')->willReturn($attributes);
if (0 < count($attributeMapping)) {
foreach ($attributeMapping as $key => $mapping) {
$this->ruleAttributeProviderMock
->expects($this->at(1 + ($key * 2)))
->method('getAttributeByCode')
->with($mapping['sourceValue'])
->willReturn(
(new AttributeBuilder())->withCode($mapping['sourceValue'])->build()
);
$this->ruleAttributeProviderMock
->expects($this->at(2 + ($key * 2)))
->method('getAttributeByCode')
->with($mapping['destinationValue'])
->willReturn(
(new AttributeBuilder())->withCode($mapping['destinationValue'])->build()
);
}
}
$this->productQueryBuilderMock->method('execute')->willReturn($destinationProducts);
$this->ruleProcessorCopierMock->expects($this->exactly($expectedCalls))->method('copy')->willReturn(true);
$processor = $this->getRuleProductProcessorInstance();
......@@ -153,7 +188,14 @@ class RuleProcessorTest extends TestCase
(new ProductBuilder())->withId(2332)->build()
);
$this->jobParametersMock
->expects($this->at(0))
->method('get')
->with('attributeMapping')
->willReturn([]);
$this->jobParametersMock
->expects($this->at(1))
->method('get')
->with('keyAttribute')
->willReturn('test');
$this->ruleAttributeProviderMock->expects($this->once())->method('getAllForFamilies')->willReturn($attributes);
$this->productQueryBuilderMock->method('execute')->willReturn([]);
......@@ -169,7 +211,14 @@ class RuleProcessorTest extends TestCase
{
$this->ruleAttributeProviderMock->expects($this->once())->method('getAllForFamilies')->willReturn($attributes);
$this->jobParametersMock
->expects($this->at(0))
->method('get')
->with('attributeMapping')
->willReturn([]);
$this->jobParametersMock
->expects($this->at(1))
->method('get')
->with('keyAttribute')
->willReturn('test');
$this->productQueryBuilderMock->method('execute')->willReturn($destinationProducts);
$this->ruleProcessorCopierMock->method('copy')->willThrowException(new \Exception());
......@@ -185,7 +234,14 @@ class RuleProcessorTest extends TestCase
{
$this->ruleAttributeProviderMock->expects($this->once())->method('getAllForFamilies')->willReturn($attributes);
$this->jobParametersMock
->expects($this->at(0))
->method('get')
->with('attributeMapping')
->willReturn([]);
$this->jobParametersMock
->expects($this->at(1))
->method('get')
->with('keyAttribute')
->willReturn('test');
$this->productQueryBuilderMock->method('addFilter')->willThrowException(new \Exception());
$this->expectException(\Throwable::class);
......@@ -201,7 +257,14 @@ class RuleProcessorTest extends TestCase
$sourceProduct = (new ProductBuilder())->build();
$this->productQueryBuilderMock->expects($this->never())->method('execute');
$this->jobParametersMock
->expects($this->at(0))
->method('get')
->with('attributeMapping')
->willReturn([]);
$this->jobParametersMock
->expects($this->at(1))
->method('get')
->with('keyAttribute')
->willReturn('test');
$processor = $this->getRuleProductProcessorInstance();
$processor->process($this->stepExecutionMock, $sourceFamily, $destinationFamily, $sourceProduct);
......@@ -215,7 +278,14 @@ class RuleProcessorTest extends TestCase
$sourceProduct = (new ProductBuilder())->build();
$this->productQueryBuilderMock->expects($this->never())->method('execute');
$this->jobParametersMock
->expects($this->at(0))
->method('get')
->with('attributeMapping')
->willReturn([]);
$this->jobParametersMock
->expects($this->at(1))
->method('get')
->with('keyAttribute')
->willReturn('test');
$processor = $this->getRuleProductProcessorInstance();
$processor->process($this->stepExecutionMock, $sourceFamily, $destinationFamily, $sourceProduct);
......
<?php
declare(strict_types=1);
/*
* Copyright (c) 2021, VillageReach
* Licensed under the Non-Profit Open Software License version 3.0.
* SPDX-License-Identifier: NPOSL-3.0
*/
namespace PcmtRulesBundle\Tests\TestDataBuilder;
use Akeneo\Pim\Structure\Component\Model\AttributeInterface;
use PcmtRulesBundle\Value\AttributeMapping;
class AttributeMappingBuilder
{
/** @var AttributeInterface */
private $sourceAttribute;
/** @var AttributeInterface */
private $destinationAttribute;
public function __construct()
{
$this->sourceAttribute = (new AttributeBuilder())->build();
$this->destinationAttribute = (new AttributeBuilder())->build();
}
public function withSourceAttribute(AttributeInterface $attribute): self
{
$this->sourceAttribute = $attribute;
return $this;
}
public function withDestinationAttribute(AttributeInterface $attribute): self
{
$this->destinationAttribute = $attribute;
return $this;
}
public function build(): AttributeMapping
{
return new AttributeMapping(
$this->sourceAttribute,
$this->destinationAttribute
);
}
}
\ No newline at end of file
<?php
declare(strict_types=1);
/*
* Copyright (c) 2021, VillageReach
* Licensed under the Non-Profit Open Software License version 3.0.
* SPDX-License-Identifier: NPOSL-3.0
*/
namespace PcmtRulesBundle\Value;
use Akeneo\Pim\Structure\Component\Model\AttributeInterface;
class AttributeMapping
{
/** @var AttributeInterface */
private $sourceAttribute;
/** @var AttributeInterface */
private $destinationAttribute;
public function __construct(
AttributeInterface $sourceAttribute,
AttributeInterface $destinationAttribute
) {
$this->sourceAttribute = $sourceAttribute;
$this->destinationAttribute = $destinationAttribute;
}
public function getSourceAttribute(): AttributeInterface
{
return $this->sourceAttribute;
}
public function getDestinationAttribute(): AttributeInterface
{
return $this->destinationAttribute;
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment