Validate bom when adding additional bom entries via addPart controller to prevent invalid BOMs

This fixes issue #302
This commit is contained in:
Jan Böhmer 2023-07-03 00:28:37 +02:00
parent 2ebb4fef4c
commit 98e179ba06
2 changed files with 95 additions and 12 deletions

View file

@ -26,21 +26,26 @@ use App\DataTables\ProjectBomEntriesDataTable;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Form\ProjectSystem\ProjectAddPartsType;
use App\Form\ProjectSystem\ProjectBOMEntryCollectionType; use App\Form\ProjectSystem\ProjectBOMEntryCollectionType;
use App\Form\ProjectSystem\ProjectBuildType; use App\Form\ProjectSystem\ProjectBuildType;
use App\Form\Type\StructuralEntityType; use App\Form\Type\StructuralEntityType;
use App\Helpers\Projects\ProjectBuildRequest; use App\Helpers\Projects\ProjectBuildRequest;
use App\Services\ImportExportSystem\BOMImporter; use App\Services\ImportExportSystem\BOMImporter;
use App\Services\ProjectSystem\ProjectBuildHelper; use App\Services\ProjectSystem\ProjectBuildHelper;
use App\Validator\Constraints\UniqueObjectCollection;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use League\Csv\SyntaxError; use League\Csv\SyntaxError;
use Omines\DataTablesBundle\DataTableFactory; use Omines\DataTablesBundle\DataTableFactory;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
@ -201,19 +206,10 @@ class ProjectController extends AbstractController
$this->denyAccessUnlessGranted('@projects.edit'); $this->denyAccessUnlessGranted('@projects.edit');
} }
$builder = $this->createFormBuilder(); $form = $this->createForm(ProjectAddPartsType::class, null, [
$builder->add('project', StructuralEntityType::class, [ 'project' => $project,
'class' => Project::class,
'required' => true,
'disabled' => $project instanceof Project, //If a project is given, disable the field
'data' => $project,
'constraints' => [
new NotNull()
]
]); ]);
$builder->add('bom_entries', ProjectBOMEntryCollectionType::class);
$builder->add('submit', SubmitType::class, ['label' => 'save']);
$form = $builder->getForm();
//Preset the BOM entries with the selected parts, when the form was not submitted yet //Preset the BOM entries with the selected parts, when the form was not submitted yet
$preset_data = new ArrayCollection(); $preset_data = new ArrayCollection();
@ -249,8 +245,11 @@ class ProjectController extends AbstractController
foreach ($bom_entries as $bom_entry){ foreach ($bom_entries as $bom_entry){
$target_project->addBOMEntry($bom_entry); $target_project->addBOMEntry($bom_entry);
} }
$entityManager->flush(); $entityManager->flush();
//If a redirect query parameter is set, redirect to this page //If a redirect query parameter is set, redirect to this page
if ($request->query->get('_redirect')) { if ($request->query->get('_redirect')) {
return $this->redirect($request->query->get('_redirect')); return $this->redirect($request->query->get('_redirect'));

View file

@ -0,0 +1,84 @@
<?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\Form\ProjectSystem;
use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Form\Type\StructuralEntityType;
use App\Validator\Constraints\UniqueObjectCollection;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotNull;
class ProjectAddPartsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('project', StructuralEntityType::class, [
'class' => Project::class,
'required' => true,
'disabled' => $options['project'] instanceof Project, //If a project is given, disable the field
'data' => $options['project'],
'constraints' => [
new NotNull()
]
]);
$builder->add('bom_entries', ProjectBOMEntryCollectionType::class, [
'entry_options' => [
'constraints' => [
new UniqueEntity(fields: ['part', 'project'], entityClass: ProjectBOMEntry::class, message: 'project.bom_entry.part_already_in_bom'),
new UniqueEntity(fields: ['name', 'project'], entityClass: ProjectBOMEntry::class, message: 'project.bom_entry.name_already_in_bom', ignoreNull: true),
]
],
'constraints' => [
new UniqueObjectCollection(fields: ['part'], message: 'project.bom_entry.part_already_in_bom'),
new UniqueObjectCollection(fields: ['name'], message: 'project.bom_entry.name_already_in_bom'),
]
]);
$builder->add('submit', SubmitType::class, ['label' => 'save']);
//After submit set the project for all bom entries, so that it can be validated properly
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
$form = $event->getForm();
/** @var Project $project */
$project = $form->get('project')->getData();
$bom_entries = $form->get('bom_entries')->getData();
foreach ($bom_entries as $bom_entry) {
$bom_entry->setProject($project);
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'project' => null,
]);
$resolver->setAllowedTypes('project', ['null', Project::class]);
}
}