mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-22 09:53:35 +02:00
Added some constraints and validations to the BOM entries.
This commit is contained in:
parent
4e3cad577e
commit
b83b55b8d4
6 changed files with 99 additions and 10 deletions
|
@ -20,7 +20,7 @@ final class Version20221218192108 extends AbstractMigration
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
$this->addSql('ALTER TABLE device_parts ADD name LONGTEXT NOT NULL, ADD comment LONGTEXT NOT NULL, ADD last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, ADD datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CHANGE quantity quantity DOUBLE PRECISION NOT NULL');
|
$this->addSql('ALTER TABLE device_parts ADD name VARCHAR(255) NULL, ADD comment LONGTEXT NOT NULL, ADD last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, ADD datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CHANGE quantity quantity DOUBLE PRECISION NOT NULL');
|
||||||
$this->addSql('ALTER TABLE devices ADD description LONGTEXT NOT NULL');
|
$this->addSql('ALTER TABLE devices ADD description LONGTEXT NOT NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AttachmentType.
|
* Class AttachmentType.
|
||||||
|
@ -53,6 +54,7 @@ class Project extends AbstractStructuralDBElement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\OneToMany(targetEntity="ProjectBOMEntry", mappedBy="project", cascade={"persist", "remove"}, orphanRemoval=true)
|
* @ORM\OneToMany(targetEntity="ProjectBOMEntry", mappedBy="project", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||||
|
* @Assert\Valid()
|
||||||
*/
|
*/
|
||||||
protected $bom_entries;
|
protected $bom_entries;
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,9 @@ use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Base\TimestampTrait;
|
use App\Entity\Base\TimestampTrait;
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ProjectBOMEntry class represents a entry in a project's BOM.
|
* The ProjectBOMEntry class represents a entry in a project's BOM.
|
||||||
|
@ -34,6 +36,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||||
* @ORM\Table("device_parts")
|
* @ORM\Table("device_parts")
|
||||||
* @ORM\HasLifecycleCallbacks()
|
* @ORM\HasLifecycleCallbacks()
|
||||||
* @ORM\Entity()
|
* @ORM\Entity()
|
||||||
|
* @UniqueEntity(fields={"part", "project"}, message="project.bom_entry.part_already_in_bom")
|
||||||
|
* @UniqueEntity(fields={"name", "project"}, message="project.bom_entry.name_already_in_bom", ignoreNull=true)
|
||||||
*/
|
*/
|
||||||
class ProjectBOMEntry extends AbstractDBElement
|
class ProjectBOMEntry extends AbstractDBElement
|
||||||
{
|
{
|
||||||
|
@ -42,7 +46,7 @@ class ProjectBOMEntry extends AbstractDBElement
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
* @ORM\Column(type="float", name="quantity")
|
* @ORM\Column(type="float", name="quantity")
|
||||||
* @Assert\PositiveOrZero()
|
* @Assert\Positive()
|
||||||
*/
|
*/
|
||||||
protected float $quantity;
|
protected float $quantity;
|
||||||
|
|
||||||
|
@ -54,9 +58,13 @@ class ProjectBOMEntry extends AbstractDBElement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string An optional name describing this BOM entry (useful for non-part entries)
|
* @var string An optional name describing this BOM entry (useful for non-part entries)
|
||||||
* @ORM\Column(type="text")
|
* @ORM\Column(type="string", nullable=true)
|
||||||
|
* @Assert\Expression(
|
||||||
|
* "this.getPart() !== null or this.getName() !== null",
|
||||||
|
* message="validator.project.bom_entry.name_or_part_needed"
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
protected string $name;
|
protected ?string $name = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string An optional comment for this BOM entry
|
* @var string An optional comment for this BOM entry
|
||||||
|
@ -117,7 +125,7 @@ class ProjectBOMEntry extends AbstractDBElement
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getName(): string
|
public function getName(): ?string
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +134,7 @@ class ProjectBOMEntry extends AbstractDBElement
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return ProjectBOMEntry
|
* @return ProjectBOMEntry
|
||||||
*/
|
*/
|
||||||
public function setName(string $name): ProjectBOMEntry
|
public function setName(?string $name): ProjectBOMEntry
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -188,5 +196,37 @@ class ProjectBOMEntry extends AbstractDBElement
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Assert\Callback
|
||||||
|
*/
|
||||||
|
public function validate(ExecutionContextInterface $context, $payload)
|
||||||
|
{
|
||||||
|
//Round quantity to whole numbers, if the part is not a decimal part
|
||||||
|
if ($this->part) {
|
||||||
|
if (!$this->part->getPartUnit() || $this->part->getPartUnit()->isInteger()) {
|
||||||
|
$this->quantity = round($this->quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check that every part name in the mountnames list is unique (per bom_entry)
|
||||||
|
$mountnames = explode(',', $this->mountnames);
|
||||||
|
$mountnames = array_map('trim', $mountnames);
|
||||||
|
$uniq_mountnames = array_unique($mountnames);
|
||||||
|
|
||||||
|
//If the number of unique names is not the same as the number of names, there are duplicates
|
||||||
|
if (count($mountnames) !== count($uniq_mountnames)) {
|
||||||
|
$context->buildViolation('project.bom_entry.mountnames_not_unique')
|
||||||
|
->atPath('mountnames')
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check that the number of mountnames is the same as the (rounded) quantity
|
||||||
|
if (!empty($this->mountnames) && count($uniq_mountnames) !== (int) round ($this->quantity)) {
|
||||||
|
$context->buildViolation('project.bom_entry.mountnames_quantity_mismatch')
|
||||||
|
->atPath('mountnames')
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,15 @@ namespace App\Form\ProjectSystem;
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||||
use App\Form\Type\PartSelectType;
|
use App\Form\Type\PartSelectType;
|
||||||
|
use App\Form\Type\SIUnitType;
|
||||||
use Svg\Tag\Text;
|
use Svg\Tag\Text;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Event\PreSetDataEvent;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\FormEvents;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
class ProjectBOMEntryType extends AbstractType
|
class ProjectBOMEntryType extends AbstractType
|
||||||
|
@ -18,11 +21,20 @@ class ProjectBOMEntryType extends AbstractType
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
$builder
|
|
||||||
|
|
||||||
->add('quantity', NumberType::class, [
|
|
||||||
|
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) {
|
||||||
|
$form = $event->getForm();
|
||||||
|
/** @var ProjectBOMEntry $data */
|
||||||
|
$data = $event->getData();
|
||||||
|
|
||||||
|
$form->add('quantity', SIUnitType::class, [
|
||||||
'label' => 'project.bom.quantity',
|
'label' => 'project.bom.quantity',
|
||||||
])
|
'measurement_unit' => $data && $data->getPart() ? $data->getPart()->getPartUnit() : null,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$builder
|
||||||
|
|
||||||
->add('part', PartSelectType::class, [
|
->add('part', PartSelectType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
@ -31,7 +43,6 @@ class ProjectBOMEntryType extends AbstractType
|
||||||
->add('name', TextType::class, [
|
->add('name', TextType::class, [
|
||||||
'label' => 'project.bom.name',
|
'label' => 'project.bom.name',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'empty_data' => ''
|
|
||||||
])
|
])
|
||||||
->add('mountnames', TextType::class, [
|
->add('mountnames', TextType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
|
|
@ -9959,5 +9959,17 @@ Element 3</target>
|
||||||
<target>Mount names</target>
|
<target>Mount names</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
|
<unit id="29QITRx" name="project.bom.name">
|
||||||
|
<segment>
|
||||||
|
<source>project.bom.name</source>
|
||||||
|
<target>Name</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="PucC1gg" name="project.bom.comment">
|
||||||
|
<segment>
|
||||||
|
<source>project.bom.comment</source>
|
||||||
|
<target>Notes</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -239,5 +239,29 @@
|
||||||
<target>The internal part number must be unique. {{ value }} is already in use!</target>
|
<target>The internal part number must be unique. {{ value }} is already in use!</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
|
<unit id="Z4Kuuo2" name="validator.project.bom_entry.name_or_part_needed">
|
||||||
|
<segment>
|
||||||
|
<source>validator.project.bom_entry.name_or_part_needed</source>
|
||||||
|
<target>You have to choose a part for a part BOM entry or set a name for a non-part BOM entry.</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="WF_v4ih" name="project.bom_entry.name_already_in_bom">
|
||||||
|
<segment>
|
||||||
|
<source>project.bom_entry.name_already_in_bom</source>
|
||||||
|
<target>There is already an BOM entry with this name!</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="5v4p85H" name="project.bom_entry.part_already_in_bom">
|
||||||
|
<segment>
|
||||||
|
<source>project.bom_entry.part_already_in_bom</source>
|
||||||
|
<target>This part already exists in the BOM!</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
|
<unit id="3lM32Tw" name="project.bom_entry.mountnames_quantity_mismatch">
|
||||||
|
<segment>
|
||||||
|
<source>project.bom_entry.mountnames_quantity_mismatch</source>
|
||||||
|
<target>The number of mountnames has to match the BOMs quantity!</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue