mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-11 04:39:33 +00:00
Füge Validierung für zyklische Baugruppenreferenzen hinzu
Eine neue Validierung wurde implementiert, um zyklische Referenzen in Baugruppen zu erkennen. Entsprechende Fehlertexte wurden in allen unterstützten Sprachen hinzugefügt. Zudem wurde der Validator in die Entität AssemblyBOMEntry integriert.
This commit is contained in:
parent
9d9cedd222
commit
cbfe1d4cc8
17 changed files with 257 additions and 0 deletions
|
|
@ -168,6 +168,9 @@ services:
|
|||
arguments:
|
||||
$useAssemblyIpnPlaceholder: '%partdb.create_assembly_use_ipn_placeholder_in_name%'
|
||||
|
||||
App\Validator\Constraints\AssemblySystem\AssemblyCycleValidator:
|
||||
tags: [ 'validator.constraint_validator' ]
|
||||
|
||||
####################################################################################################################
|
||||
# Table settings
|
||||
####################################################################################################################
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\Entity\AssemblySystem;
|
||||
|
||||
use App\Repository\AssemblyRepository;
|
||||
use App\Validator\Constraints\AssemblySystem\AssemblyCycle;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
|
|
@ -113,6 +114,7 @@ class Assembly extends AbstractStructuralDBElement
|
|||
#[Groups(['extended', 'full', 'import'])]
|
||||
#[ORM\OneToMany(targetEntity: AssemblyBOMEntry::class, mappedBy: 'assembly', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.part_already_in_bom', fields: ['part'])]
|
||||
#[AssemblyCycle]
|
||||
#[UniqueReferencedAssembly]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.project_already_in_bom', fields: ['project'])]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.name_already_in_bom', fields: ['name'])]
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ use App\ApiPlatform\Filter\LikeFilter;
|
|||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Repository\DBElementRepository;
|
||||
use App\Validator\Constraints\AssemblySystem\AssemblyCycle;
|
||||
use App\Validator\UniqueValidatableInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
|
|
@ -140,6 +141,7 @@ class AssemblyBOMEntry extends AbstractDBElement implements UniqueValidatableInt
|
|||
'(this.getPart() === null or this.getReferencedAssembly() === null) and (this.getName() === null or (this.getName() != null and this.getName() != ""))',
|
||||
message: 'validator.assembly.bom_entry.only_part_or_assembly_allowed'
|
||||
)]
|
||||
#[AssemblyCycle]
|
||||
#[ORM\ManyToOne(targetEntity: Assembly::class)]
|
||||
#[ORM\JoinColumn(name: 'id_referenced_assembly', nullable: true, onDelete: 'SET NULL')]
|
||||
#[Groups(['bom_entry:read', 'bom_entry:write', ])]
|
||||
|
|
|
|||
39
src/Validator/Constraints/AssemblySystem/AssemblyCycle.php
Normal file
39
src/Validator/Constraints/AssemblySystem/AssemblyCycle.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Validator\Constraints\AssemblySystem;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* This constraint checks that there is no cycle in bom configuration of the assembly
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class AssemblyCycle extends Constraint
|
||||
{
|
||||
public string $message = 'assembly.bom_entry.assembly_cycle';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return AssemblyCycleValidator::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Validator\Constraints\AssemblySystem;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Validator class to check for cycles in assemblies based on BOM entries.
|
||||
*
|
||||
* This validator ensures that the structure of assemblies does not contain circular dependencies
|
||||
* by validating each entry in the Bill of Materials (BOM) of the given assembly. Additionally,
|
||||
* it can handle form-submitted BOM entries to include these in the validation process.
|
||||
*/
|
||||
class AssemblyCycleValidator extends ConstraintValidator
|
||||
{
|
||||
public function validate($value, Constraint $constraint): void
|
||||
{
|
||||
if (!$constraint instanceof AssemblyCycle) {
|
||||
throw new UnexpectedTypeException($constraint, AssemblyCycle::class);
|
||||
}
|
||||
|
||||
if (!$value instanceof Assembly) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bomEntries = $value->getBomEntries()->toArray();
|
||||
|
||||
// Consider additional entries from the form
|
||||
if ($this->context->getRoot()->has('bom_entries')) {
|
||||
$formBomEntries = $this->context->getRoot()->get('bom_entries')->getData();
|
||||
if ($formBomEntries) {
|
||||
$given = is_array($formBomEntries) ? $formBomEntries : iterator_to_array($formBomEntries);
|
||||
foreach ($given as $givenIdx => $entry) {
|
||||
if (in_array($entry, $bomEntries, true)) {
|
||||
continue;
|
||||
} else {
|
||||
$bomEntries[$givenIdx] = $entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$visitedAssemblies = [];
|
||||
foreach ($bomEntries as $bomEntry) {
|
||||
if ($this->hasCycle($bomEntry->getReferencedAssembly(), $value, $visitedAssemblies)) {
|
||||
$this->addViolation($value, $constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function hasCycle(?Assembly $currentAssembly, Assembly $originalAssembly, array &$visitedAssemblies): bool
|
||||
{
|
||||
if ($currentAssembly === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($currentAssembly, $visitedAssemblies, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$visitedAssemblies[] = $currentAssembly;
|
||||
|
||||
foreach ($currentAssembly->getBomEntries() as $bomEntry) {
|
||||
if ($this->hasCycle($bomEntry->getReferencedAssembly(), $originalAssembly, $visitedAssemblies)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a violation to the current context if it hasn’t already been added.
|
||||
*
|
||||
* This method checks whether a violation with the same property path as the current violation
|
||||
* already exists in the context. If such a violation is found, the current violation is not added again.
|
||||
* The process involves reflection to access private or protected properties of violation objects.
|
||||
*
|
||||
* @param mixed $value The value that triggered the violation.
|
||||
* @param Constraint $constraint The constraint containing the validation details.
|
||||
*
|
||||
*/
|
||||
private function addViolation($value, Constraint $constraint): void
|
||||
{
|
||||
/** @var ConstraintViolationBuilder $buildViolation */
|
||||
$buildViolation = $this->context->buildViolation($constraint->message)
|
||||
->setParameter('%name%', $value->getName());
|
||||
|
||||
$alreadyAdded = false;
|
||||
|
||||
try {
|
||||
$reflectionClass = new ReflectionClass($buildViolation);
|
||||
$property = $reflectionClass->getProperty('propertyPath');
|
||||
$propertyPath = $property->getValue($buildViolation);
|
||||
|
||||
$availableViolations = $this->context->getViolations();
|
||||
|
||||
foreach ($availableViolations as $tmpViolation) {
|
||||
$tmpReflectionClass = new ReflectionClass($tmpViolation);
|
||||
$tmpProperty = $tmpReflectionClass->getProperty('propertyPath');
|
||||
$tmpPropertyPath = $tmpProperty->getValue($tmpViolation);
|
||||
|
||||
if ($tmpPropertyPath === $propertyPath) {
|
||||
$alreadyAdded = true;
|
||||
}
|
||||
}
|
||||
} catch (\ReflectionException) {
|
||||
}
|
||||
|
||||
if (!$alreadyAdded) {
|
||||
$buildViolation->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -395,6 +395,12 @@
|
|||
<target>Tato sestava již existuje jako položka v seznamu materiálů!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>Byl zjištěn cyklus: Sestava "%name%" nepřímo odkazuje sama na sebe.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -371,6 +371,12 @@
|
|||
<target>Denne samling findes allerede som en post!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>En cyklus blev opdaget: Samlingen "%name%" refererer indirekte til sig selv.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
<target>Diese Baugruppe existiert bereits als Eintrag!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>Ein Zyklus wurde entdeckt: Die Baugruppe "%name%" referenziert sich indirekt selbst.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,12 @@
|
|||
<target>Αυτή η συναρμολόγηση υπάρχει ήδη ως εγγραφή!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>Εντοπίστηκε κύκλος: Η συναρμολόγηση "%name%" αναφέρεται έμμεσα στον εαυτό της.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
<target>This assembly already exists as an entry!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>A cycle was detected: the assembly "%name%" indirectly references itself.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -227,6 +227,12 @@
|
|||
<target>Cet assemblage existe déjà en tant qu'entrée !</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>Un cycle a été détecté : L'assemblage "%name%" se réfère indirectement à lui-même.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -389,6 +389,12 @@
|
|||
<target>Ova se montaža već nalazi kao zapis!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>Otkriven je ciklus: Sklop "%name%" neizravno referencira samog sebe.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -389,6 +389,12 @@
|
|||
<target>Questo assemblaggio è già presente come voce!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>È stato rilevato un ciclo: L'assemblaggio "%name%" fa riferimento indirettamente a sé stesso.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -227,6 +227,12 @@
|
|||
<target>このアセンブリはすでにエントリとして存在します!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>循環が検出されました: アセンブリ「%name%」が間接的に自身を参照しています。</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -389,6 +389,12 @@
|
|||
<target>To zestawienie jest już dodane jako wpis!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>循環が検出されました: アセンブリ「%name%」が間接的に自身を参照しています。</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -389,6 +389,12 @@
|
|||
<target>Этот сборочный узел уже добавлен как запись!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>Обнаружен цикл: Сборка «%name%» косвенно ссылается на саму себя.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
|
|
@ -377,6 +377,12 @@
|
|||
<target>此装配已经作为条目存在!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="j8ATef1" name="assembly.bom_entry.assembly_cycle">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.assembly_cycle</source>
|
||||
<target>检测到循环:装配体“%name%”间接引用了其自身。</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6bkQ3bo" name="assembly.bom_entry.project_already_in_bom">
|
||||
<segment>
|
||||
<source>assembly.bom_entry.project_already_in_bom</source>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue