Added some constraints and validations to the BOM entries.

This commit is contained in:
Jan Böhmer 2022-12-26 13:57:11 +01:00
parent 4e3cad577e
commit b83b55b8d4
6 changed files with 99 additions and 10 deletions

View file

@ -20,7 +20,7 @@ final class Version20221218192108 extends AbstractMigration
public function up(Schema $schema): void
{
// 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');
}

View file

@ -29,6 +29,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class AttachmentType.
@ -53,6 +54,7 @@ class Project extends AbstractStructuralDBElement
/**
* @ORM\OneToMany(targetEntity="ProjectBOMEntry", mappedBy="project", cascade={"persist", "remove"}, orphanRemoval=true)
* @Assert\Valid()
*/
protected $bom_entries;

View file

@ -26,7 +26,9 @@ use App\Entity\Base\AbstractDBElement;
use App\Entity\Base\TimestampTrait;
use App\Entity\Parts\Part;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* 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\HasLifecycleCallbacks()
* @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
{
@ -42,7 +46,7 @@ class ProjectBOMEntry extends AbstractDBElement
/**
* @var int
* @ORM\Column(type="float", name="quantity")
* @Assert\PositiveOrZero()
* @Assert\Positive()
*/
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)
* @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
@ -117,7 +125,7 @@ class ProjectBOMEntry extends AbstractDBElement
/**
* @return string
*/
public function getName(): string
public function getName(): ?string
{
return $this->name;
}
@ -126,7 +134,7 @@ class ProjectBOMEntry extends AbstractDBElement
* @param string $name
* @return ProjectBOMEntry
*/
public function setName(string $name): ProjectBOMEntry
public function setName(?string $name): ProjectBOMEntry
{
$this->name = $name;
return $this;
@ -188,5 +196,37 @@ class ProjectBOMEntry extends AbstractDBElement
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();
}
}
}

View file

@ -5,12 +5,15 @@ namespace App\Form\ProjectSystem;
use App\Entity\Parts\Part;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Form\Type\PartSelectType;
use App\Form\Type\SIUnitType;
use Svg\Tag\Text;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
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\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProjectBOMEntryType extends AbstractType
@ -18,11 +21,20 @@ class ProjectBOMEntryType extends AbstractType
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',
])
'measurement_unit' => $data && $data->getPart() ? $data->getPart()->getPartUnit() : null,
]);
});
$builder
->add('part', PartSelectType::class, [
'required' => false,
@ -31,7 +43,6 @@ class ProjectBOMEntryType extends AbstractType
->add('name', TextType::class, [
'label' => 'project.bom.name',
'required' => false,
'empty_data' => ''
])
->add('mountnames', TextType::class, [
'required' => false,

View file

@ -9959,5 +9959,17 @@ Element 3</target>
<target>Mount names</target>
</segment>
</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>
</xliff>

View file

@ -239,5 +239,29 @@
<target>The internal part number must be unique. {{ value }} is already in use!</target>
</segment>
</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>
</xliff>