diff --git a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php index bdc18bac..2eb3ff88 100644 --- a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php +++ b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php @@ -23,6 +23,11 @@ declare(strict_types=1); namespace App\Services\EntityMergers\Mergers; +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentContainingDBElement; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parts\Part; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -153,9 +158,12 @@ trait EntityMergerHelperTrait * @param object $target * @param object $other * @param string $field + * @param callable|null $equal_fn A function, which checks if two items are equal. The signature is: function(object $target, object other): bool. + * Return true if the items are equal, false otherwise. If two items are equal, the item from the other collection is not added to the target collection. + * If null, the items are compared by (instance) identity. * @return object */ - protected function mergeCollections(object $target, object $other, string $field): object + protected function mergeCollections(object $target, object $other, string $field, ?callable $equal_fn = null): object { $target_collection = $this->property_accessor->getValue($target, $field); $other_collection = $this->property_accessor->getValue($other, $field); @@ -167,6 +175,19 @@ trait EntityMergerHelperTrait //Clone the items from the other collection $clones = []; foreach ($other_collection as $item) { + //Check if the item is already in the target collection + if ($equal_fn !== null) { + foreach ($target_collection as $target_item) { + if ($equal_fn($target_item, $item)) { + continue 2; + } + } + } else { + if ($target_collection->contains($item)) { + continue; + } + } + $clones[] = clone $item; } @@ -177,4 +198,39 @@ trait EntityMergerHelperTrait return $target; } + + /** + * Merge the attachments from the target and the other entity. + * @param AttachmentContainingDBElement $target + * @param AttachmentContainingDBElement $other + * @return object + */ + protected function mergeAttachments(AttachmentContainingDBElement $target, AttachmentContainingDBElement $other): object + { + return $this->mergeCollections($target, $other, 'attachments', function (Attachment $t, Attachment $o): bool { + return $t->getName() === $o->getName() + && $t->getAttachmentType() === $o->getAttachmentType() + && $t->getPath() === $o->getPath(); + }); + } + + /** + * Merge the parameters from the target and the other entity. + * @param AbstractStructuralDBElement|Part $target + * @param AbstractStructuralDBElement|Part $other + * @return object + */ + protected function mergeParameters(AbstractStructuralDBElement|Part $target, AbstractStructuralDBElement|Part $other): object + { + return $this->mergeCollections($target, $other, 'parameters', function (AbstractParameter $t, AbstractParameter $o): bool { + return $t->getName() === $o->getName() + && $t->getSymbol() === $o->getSymbol() + && $t->getUnit() === $o->getUnit() + && $t->getValueMax() === $o->getValueMax() + && $t->getValueMin() === $o->getValueMin() + && $t->getValueTypical() === $o->getValueTypical() + && $t->getValueText() === $o->getValueText() + && $t->getGroup() === $o->getGroup(); + }); + } } \ No newline at end of file diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php index 1b31815a..c25d0dc6 100644 --- a/src/Services/EntityMergers/Mergers/PartMerger.php +++ b/src/Services/EntityMergers/Mergers/PartMerger.php @@ -24,6 +24,9 @@ declare(strict_types=1); namespace App\Services\EntityMergers\Mergers; use App\Entity\Parts\Part; +use App\Entity\Parts\PartAssociation; +use App\Entity\Parts\PartLot; +use App\Entity\PriceInformations\Orderdetail; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; #[Autoconfigure(public: true)] @@ -43,14 +46,77 @@ class PartMerger implements EntityMergerInterface throw new \InvalidArgumentException('The target and the other entity must be instances of Part'); } - //Merge the fields + //Merge basic infos $this->useOtherValueIfNotNull($target, $other, 'manufacturer'); + $this->useOtherValueIfNotNull($target, $other, 'footprint'); + $this->useOtherValueIfNotNull($target, $other, 'category'); + $this->useOtherValueIfNotNull($target, $other, 'partUnit'); + + //We assume that the higher value is the correct one for minimum instock + $this->useLargerValue($target, $other, 'minamount'); + - $this->mergeCollections($target, $other, 'partLots'); - $this->mergeCollections($target, $other, 'attachments'); - $this->mergeCollections($target, $other, 'orderdetails'); - $this->mergeCollections($target, $other, 'parameters'); return $target; } + + private function mergeCollectionFields(Part $target, Part $other, array $context): void + { + /******************************************************************************** + * Merge collections + ********************************************************************************/ + + //Lots from different parts are never considered equal, so we just merge them together + $this->mergeCollections($target, $other, 'partLots'); + $this->mergeAttachments($target, $other); + $this->mergeParameters($target, $other); + + $this->mergeCollections($target, $other, 'associated_parts_as_owner', function (PartAssociation $t, PartAssociation $o) { + //We compare the translation keys, as it contains info about the type and other type info + return $t->getOther() === $o->getOther() + && $t->getTypeTranslationKey() === $o->getTypeTranslationKey(); + }); + + $this->mergeCollections($target, $other, 'orderdetails', function (Orderdetail $t, Orderdetail $o) { + //First check that the orderdetails infos are equal + $tmp = $t->getSupplier() === $o->getSupplier() + && $t->getSupplierPartNr() === $o->getSupplierPartNr() + && $t->getSupplierProductUrl(false) === $o->getSupplierProductUrl(false); + + if (!$tmp) { + return false; + } + + //Check if the pricedetails are equal + $t_pricedetails = $t->getPricedetails(); + $o_pricedetails = $o->getPricedetails(); + //Ensure that both pricedetails have the same length + if (count($t_pricedetails) !== count($o_pricedetails)) { + return false; + } + + //Check if all pricedetails are equal + for ($n=0, $nMax = count($t_pricedetails); $n< $nMax; $n++) { + $t_price = $t_pricedetails->get($n); + $o_price = $o_pricedetails->get($n); + + if (!$t_price->getPrice()->isEqualTo($o_price->getPrice()) + || $t_price->getCurrency() !== $o_price->getCurrency() + || $t_price->getPriceRelatedQuantity() !== $o_price->getPriceRelatedQuantity() + || $t_price->getMinDiscountQuantity() !== $o_price->getMinDiscountQuantity() + ) { + return false; + } + } + + //If all pricedetails are equal, the orderdetails are equal + return true; + }); + //The pricedetails are not correctly assigned to the new orderdetails, so fix that + foreach ($target->getOrderdetails() as $orderdetail) { + foreach ($orderdetail->getPricedetails() as $pricedetail) { + $pricedetail->setOrderdetail($orderdetail); + } + } + } } \ No newline at end of file