mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-07-08 17:34:32 +02:00
Merge the remaining fields of a Part
This commit is contained in:
parent
478d5e2a3a
commit
c86694ab8f
4 changed files with 384 additions and 4 deletions
|
@ -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<b>%s:</b>\n%s",
|
||||
trim($t),
|
||||
$other->getName(),
|
||||
trim($o)
|
||||
);
|
||||
},
|
||||
$target,
|
||||
$other,
|
||||
$field
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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\n<b>Test2:</b>\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());
|
||||
}
|
||||
}
|
46
tests/Services/EntityMergers/Mergers/MergeTestClass.php
Normal file
46
tests/Services/EntityMergers/Mergers/MergeTestClass.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue