diff --git a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php
index ae3c75b9..b2fbff24 100644
--- a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php
+++ b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php
@@ -25,6 +25,7 @@ namespace App\Services\EntityMergers\Mergers;
use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\AttachmentContainingDBElement;
+use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\Parameters\AbstractParameter;
use App\Entity\Parts\Part;
@@ -33,6 +34,8 @@ use Doctrine\Common\Collections\Collection;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Contracts\Service\Attribute\Required;
+use function Symfony\Component\String\u;
+
/**
* This trait provides helper methods for entity mergers.
* By default, it uses the value from the target entity, unless it not fullfills a condition.
@@ -184,10 +187,10 @@ trait EntityMergerHelperTrait
protected function mergeTags(object $target, object $other, string $field, string $separator = ','): object
{
return $this->useCallback(
- function (string $t, string $o) use ($separator): string {
+ function (string|null $t, string|null $o) use ($separator): string {
//Explode the strings into arrays
- $t_array = explode($separator, $t);
- $o_array = explode($separator, $o);
+ $t_array = explode($separator, $t ?? '');
+ $o_array = explode($separator, $o ?? '');
//Merge the arrays and remove duplicates
$tmp = array_unique(array_merge($t_array, $o_array));
@@ -281,4 +284,75 @@ trait EntityMergerHelperTrait
&& $t->getGroup() === $o->getGroup();
});
}
+
+ /**
+ * Check if the two strings have equal content.
+ * This method is case-insensitive and ignores whitespace.
+ * @param string|\Stringable $t
+ * @param string|\Stringable $o
+ * @return bool
+ */
+ protected function areStringsEqual(string|\Stringable $t, string|\Stringable $o): bool
+ {
+ $t_str = u($t)->trim()->folded();
+ $o_str = u($o)->trim()->folded();
+
+ return $t_str->equalsTo($o_str);
+ }
+
+ /**
+ * Merge the text from the target and the other entity for the given field by attaching the other text to the target text via the given separator.
+ * For example, if the target text is "Hello" and the other text is "World", the result is "Hello / World".
+ * If the text is the same in both entities, the target text is returned.
+ * @param object $target
+ * @param object $other
+ * @param string $field
+ * @param string $separator
+ * @return object
+ */
+ protected function mergeTextWithSeparator(object $target, object $other, string $field, string $separator = ' / '): object
+ {
+ return $this->useCallback(
+ function (string $t, string $o) use ($separator): string {
+ //Check if the strings are equal
+ if ($this->areStringsEqual($t, $o)) {
+ return $t;
+ }
+
+ return trim($t) . $separator . trim($o);
+ },
+ $target,
+ $other,
+ $field
+ );
+ }
+
+ /**
+ * Merge two comments from the target and the other entity for the given field.
+ * The comments of the both entities get concated, while the second part get a headline with the name of the old part.
+ * @param AbstractNamedDBElement $target
+ * @param AbstractNamedDBElement $other
+ * @param string $field
+ * @return object
+ */
+ protected function mergeComment(AbstractNamedDBElement $target, AbstractNamedDBElement $other, string $field = 'comment'): object
+ {
+ return $this->useCallback(
+ function (string $t, string $o) use ($other): string {
+ //Check if the strings are equal
+ if ($this->areStringsEqual($t, $o)) {
+ return $t;
+ }
+
+ return sprintf("%s\n\n%s:\n%s",
+ trim($t),
+ $other->getName(),
+ trim($o)
+ );
+ },
+ $target,
+ $other,
+ $field
+ );
+ }
}
\ No newline at end of file
diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php
index a3962118..6c839414 100644
--- a/src/Services/EntityMergers/Mergers/PartMerger.php
+++ b/src/Services/EntityMergers/Mergers/PartMerger.php
@@ -23,6 +23,8 @@ declare(strict_types=1);
namespace App\Services\EntityMergers\Mergers;
+use App\Entity\Parts\InfoProviderReference;
+use App\Entity\Parts\ManufacturingStatus;
use App\Entity\Parts\Part;
use App\Entity\Parts\PartAssociation;
use App\Entity\Parts\PartLot;
@@ -46,7 +48,16 @@ class PartMerger implements EntityMergerInterface
throw new \InvalidArgumentException('The target and the other entity must be instances of Part');
}
- //Merge basic infos
+ //Merge basic fields
+ $this->mergeTextWithSeparator($target, $other, 'name');
+ $this->mergeTextWithSeparator($target, $other, 'description');
+ $this->mergeComment($target, $other);
+ $this->useOtherValueIfNotEmtpy($target, $other, 'manufacturer_product_url');
+ $this->useOtherValueIfNotEmtpy($target, $other, 'manufacturer_product_number');
+ $this->useOtherValueIfNotEmtpy($target, $other, 'mass');
+ $this->useOtherValueIfNotEmtpy($target, $other, 'ipn');
+
+ //Merge relations to other entities
$this->useOtherValueIfNotNull($target, $other, 'manufacturer');
$this->useOtherValueIfNotNull($target, $other, 'footprint');
$this->useOtherValueIfNotNull($target, $other, 'category');
@@ -62,6 +73,24 @@ class PartMerger implements EntityMergerInterface
//Merge the tags using the tag merger
$this->mergeTags($target, $other, 'tags');
+ //Merge manufacturing status
+ $this->useCallback(function (?ManufacturingStatus $t, ?ManufacturingStatus $o): ?ManufacturingStatus {
+ //Use the other value, if the target value is not set
+ if ($t === ManufacturingStatus::NOT_SET || $t === null) {
+ return $o ?? ManufacturingStatus::NOT_SET;
+ }
+
+ return $t;
+ }, $target, $other, 'manufacturing_status');
+
+ //Merge provider reference
+ $this->useCallback(function (InfoProviderReference $t, InfoProviderReference $o): InfoProviderReference {
+ if (!$t->isProviderCreated() && $o->isProviderCreated()) {
+ return $o;
+ }
+ return $t;
+ }, $target, $other, 'providerReference');
+
return $target;
}
diff --git a/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php b/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php
new file mode 100644
index 00000000..c8366019
--- /dev/null
+++ b/tests/Services/EntityMergers/Mergers/EntityMergerHelperTraitTest.php
@@ -0,0 +1,231 @@
+.
+ */
+
+namespace App\Tests\Services\EntityMergers\Mergers;
+
+use App\Entity\Parts\Part;
+use App\Services\EntityMergers\Mergers\EntityMergerHelperTrait;
+use PHPUnit\Framework\TestCase;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+
+class EntityMergerHelperTraitTest extends KernelTestCase
+{
+ use EntityMergerHelperTrait;
+
+ public function setUp(): void
+ {
+ self::bootKernel();
+ $this->property_accessor = self::getContainer()->get(PropertyAccessorInterface::class);
+ }
+
+ public function testUseCallback(): void
+ {
+ $obj1 = new MergeTestClass();
+ $obj1->non_nullable_string = 'obj1';
+ $obj2 = new MergeTestClass();
+ $obj2->non_nullable_string = 'obj2';
+
+ $tmp = $this->useCallback(function ($target_value, $other_value, $target, $other, $field) use ($obj1, $obj2) {
+ $this->assertSame($obj1, $target);
+ $this->assertSame($obj2, $other);
+ $this->assertSame('non_nullable_string', $field);
+ $this->assertSame('obj1', $target_value);
+ $this->assertSame('obj2', $other_value);
+
+ return 'callback';
+
+ }, $obj1, $obj2, 'non_nullable_string');
+
+ //merge should return the target object
+ $this->assertSame($obj1, $tmp);
+ //And it should have the value from the callback set
+ $this->assertSame('callback', $obj1->non_nullable_string);
+ }
+
+ public function testOtherFunctionIfNotNull(): void
+ {
+ $obj1 = new MergeTestClass();
+ $obj1->string_property = null;
+ $obj2 = new MergeTestClass();
+ $obj2->string_property = 'obj2';
+
+ $tmp = $this->useOtherValueIfNotNull($obj1, $obj2, 'string_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame('obj2', $obj1->string_property);
+
+ $obj1->string_property = 'obj1';
+ $tmp = $this->useOtherValueIfNotNull($obj1, $obj2, 'string_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame('obj1', $tmp->string_property);
+
+ $obj1->string_property = null;
+ $obj2->string_property = null;
+ $this->assertSame($obj1, $this->useOtherValueIfNotNull($obj1, $obj2, 'string_property'));
+ $this->assertNull($obj1->string_property);
+ }
+
+ public function testOtherFunctionIfNotEmpty(): void
+ {
+ $obj1 = new MergeTestClass();
+ $obj1->string_property = null;
+ $obj2 = new MergeTestClass();
+ $obj2->string_property = 'obj2';
+
+ $tmp = $this->useOtherValueIfNotEmtpy($obj1, $obj2, 'string_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame('obj2', $obj1->string_property);
+
+ $obj1->string_property = 'obj1';
+ $tmp = $this->useOtherValueIfNotEmtpy($obj1, $obj2, 'string_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame('obj1', $tmp->string_property);
+
+ $obj1->string_property = null;
+ $obj2->string_property = null;
+ $this->assertSame($obj1, $this->useOtherValueIfNotEmtpy($obj1, $obj2, 'string_property'));
+ $this->assertNull($obj1->string_property);
+
+ $obj1->string_property = '';
+ $obj2->string_property = 'test';
+ $this->assertSame($obj1, $this->useOtherValueIfNotEmtpy($obj1, $obj2, 'string_property'));
+ $this->assertSame('test', $obj1->string_property);
+ }
+
+ public function testUseLargerValue(): void
+ {
+ $obj1 = new MergeTestClass();
+ $obj1->int_property = 1;
+ $obj2 = new MergeTestClass();
+ $obj2->int_property = 2;
+
+ $tmp = $this->useLargerValue($obj1, $obj2, 'int_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame(2, $obj1->int_property);
+
+ $obj1->int_property = 3;
+ $obj2->int_property = 2;
+
+ $tmp = $this->useLargerValue($obj1, $obj2, 'int_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame(3, $obj1->int_property);
+ }
+
+ public function testUseSmallerValue(): void
+ {
+ $obj1 = new MergeTestClass();
+ $obj1->int_property = 1;
+ $obj2 = new MergeTestClass();
+ $obj2->int_property = 2;
+
+ $tmp = $this->useSmallerValue($obj1, $obj2, 'int_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame(1, $obj1->int_property);
+
+ $obj1->int_property = 3;
+ $obj2->int_property = 2;
+
+ $tmp = $this->useSmallerValue($obj1, $obj2, 'int_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame(2, $obj1->int_property);
+ }
+
+ public function testUseTrueValue(): void
+ {
+ $obj1 = new MergeTestClass();
+ $obj1->bool_property = false;
+ $obj2 = new MergeTestClass();
+ $obj2->bool_property = true;
+
+ $tmp = $this->useTrueValue($obj1, $obj2, 'bool_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertTrue($obj1->bool_property);
+
+ $obj1->bool_property = true;
+ $obj2->bool_property = false;
+ $this->assertTrue($this->useTrueValue($obj1, $obj2, 'bool_property')->bool_property);
+
+ $obj1->bool_property = false;
+ $obj2->bool_property = false;
+ $this->assertFalse($this->useTrueValue($obj1, $obj2, 'bool_property')->bool_property);
+ }
+
+ public function testMergeTags(): void
+ {
+ $obj1 = new MergeTestClass();
+ $obj1->string_property = 'tag1,tag2,tag3';
+ $obj2 = new MergeTestClass();
+ $obj2->string_property = 'tag2,tag3,tag4';
+
+ $tmp = $this->mergeTags($obj1, $obj2, 'string_property');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame('tag1,tag2,tag3,tag4', $obj1->string_property);
+ }
+
+ public function testAreStringsEqual(): void
+ {
+ $this->assertTrue($this->areStringsEqual('test', 'test'));
+ $this->assertTrue($this->areStringsEqual('test', 'TEST'));
+ $this->assertTrue($this->areStringsEqual('test', 'Test'));
+ $this->assertTrue($this->areStringsEqual('test', ' Test '));
+ $this->assertTrue($this->areStringsEqual('Test ', 'test'));
+
+ $this->assertFalse($this->areStringsEqual('test', 'test2'));
+ $this->assertFalse($this->areStringsEqual('test', 'test 1'));
+ }
+
+ public function testMergeTextWithSeparator(): void
+ {
+ $obj1 = new MergeTestClass();
+ $obj1->string_property = 'Test1';
+ $obj2 = new MergeTestClass();
+ $obj2->string_property = 'Test2';
+
+ $tmp = $this->mergeTextWithSeparator($obj1, $obj2, 'string_property', ' # ');
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame('Test1 # Test2', $obj1->string_property);
+
+ //If thee text is the same, it should not be duplicated
+ $obj1->string_property = 'Test1';
+ $obj2->string_property = 'Test1';
+ $this->assertSame($obj1, $this->mergeTextWithSeparator($obj1, $obj2, 'string_property', ' # '));
+ $this->assertSame('Test1', $obj1->string_property);
+ }
+
+ public function testMergeComment(): void
+ {
+ $obj1 = new Part();
+ $obj1->setName('Test1');
+ $obj1->setComment('Comment1');
+ $obj2 = new Part();
+ $obj2->setName('Test2');
+ $obj2->setComment('Comment2');
+
+ $tmp = $this->mergeComment($obj1, $obj2);
+ $this->assertSame($obj1, $tmp);
+ $this->assertSame("Comment1\n\nTest2:\nComment2", $obj1->getComment());
+
+ //If the comment is the same, it should not be duplicated
+ $obj1->setComment('Comment1');
+ $obj2->setComment('Comment1');
+ $this->assertSame($obj1, $this->mergeComment($obj1, $obj2));
+ $this->assertSame('Comment1', $obj1->getComment());
+ }
+}
diff --git a/tests/Services/EntityMergers/Mergers/MergeTestClass.php b/tests/Services/EntityMergers/Mergers/MergeTestClass.php
new file mode 100644
index 00000000..da7ad67c
--- /dev/null
+++ b/tests/Services/EntityMergers/Mergers/MergeTestClass.php
@@ -0,0 +1,46 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Tests\Services\EntityMergers\Mergers;
+
+use App\Entity\Parts\Category;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+
+class MergeTestClass
+{
+ public int $int_property = 0;
+ public ?float $float_property = null;
+ public ?string $string_property = null;
+ public string $non_nullable_string = '';
+ public bool $bool_property = false;
+
+ public Collection $collection;
+
+ public ?Category $category;
+
+ public function __construct()
+ {
+ $this->collection = new ArrayCollection();
+ }
+}
\ No newline at end of file