diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index f39882d2..92d5dd5d 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -343,7 +343,7 @@ abstract class BaseAdminController extends AbstractController 'preserve_children' => $data['preserve_children'], 'format' => $data['format'], 'class' => $this->entity_class, - 'csv_separator' => $data['csv_separator'], + 'csv_delimiter' => $data['csv_delimiter'], ]; $this->commentHelper->setMessage('Import '.$file->getClientOriginalName()); diff --git a/src/Controller/PartImportExportController.php b/src/Controller/PartImportExportController.php index e3013cb8..9abf88d2 100644 --- a/src/Controller/PartImportExportController.php +++ b/src/Controller/PartImportExportController.php @@ -20,24 +20,76 @@ namespace App\Controller; +use App\Entity\Parts\Part; +use App\Form\AdminPages\ImportType; use App\Services\ImportExportSystem\EntityExporter; +use App\Services\ImportExportSystem\EntityImporter; +use App\Services\LogSystem\EventCommentHelper; use App\Services\Parts\PartsTableActionHandler; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Validator\ConstraintViolationList; class PartImportExportController extends AbstractController { - private EntityManagerInterface $entityManager; private PartsTableActionHandler $partsTableActionHandler; + private EntityImporter $entityImporter; + private EventCommentHelper $commentHelper; - public function __construct(EntityManagerInterface $entityManager, PartsTableActionHandler $partsTableActionHandler) + public function __construct(PartsTableActionHandler $partsTableActionHandler, + EntityImporter $entityImporter, EventCommentHelper $commentHelper) { - $this->entityManager = $entityManager; $this->partsTableActionHandler = $partsTableActionHandler; + $this->entityImporter = $entityImporter; + $this->commentHelper = $commentHelper; + } + + /** + * @Route("/parts/import", name="parts_import") + * @param Request $request + * @return Response + */ + public function importParts(Request $request): Response + { + $import_form = $this->createForm(ImportType::class, ['entity_class' => Part::class]); + $import_form->handleRequest($request); + + if ($import_form->isSubmitted() && $import_form->isValid()) { + /** @var UploadedFile $file */ + $file = $import_form['file']->getData(); + $data = $import_form->getData(); + + $options = [ + 'preserve_children' => $data['preserve_children'], + 'format' => $data['format'], + 'part_category' => $data['part_category'], + 'class' => Part::class, + 'csv_delimiter' => $data['csv_delimiter'], + ]; + + $this->commentHelper->setMessage('Import '.$file->getClientOriginalName()); + + $entities = []; + + $errors = $this->entityImporter->importFileAndPersistToDB($file, $options, $entities); + + if ($errors) { + $this->addFlash('error', 'parts.import.flash.error'); + } else { + $this->addFlash('success', 'parts.import.flash.success'); + } + } + + return $this->renderForm('parts/import/parts_import.html.twig', [ + 'import_form' => $import_form, + 'imported_entities' => $entities ?? [], + 'import_errors' => $errors ?? [], + ]); } /** diff --git a/src/Form/AdminPages/ImportType.php b/src/Form/AdminPages/ImportType.php index 03e8800b..71e088a3 100644 --- a/src/Form/AdminPages/ImportType.php +++ b/src/Form/AdminPages/ImportType.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace App\Form\AdminPages; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parts\Category; +use App\Entity\Parts\Part; use App\Form\Type\StructuralEntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -63,7 +65,7 @@ class ImportType extends AbstractType 'label' => 'export.format', 'disabled' => $disabled, ]) - ->add('csv_separator', TextType::class, [ + ->add('csv_delimiter', TextType::class, [ 'data' => ';', 'label' => 'import.csv_separator', 'disabled' => $disabled, @@ -78,6 +80,17 @@ class ImportType extends AbstractType ]); } + if ($entity instanceof Part) { + $builder->add('part_category', StructuralEntityType::class, [ + 'class' => Category::class, + 'required' => false, + 'label' => 'category.label', + 'disabled' => $disabled, + 'disable_not_selectable' => true, + 'allow_add' => true + ]); + } + $builder->add('file', FileType::class, [ 'label' => 'import.file', 'attr' => [ diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index 319b8fa1..5097c04e 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -24,6 +24,8 @@ namespace App\Services\ImportExportSystem; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parts\Category; +use App\Entity\Parts\Part; use Symplify\EasyCodingStandard\ValueObject\Option; use function count; use Doctrine\ORM\EntityManagerInterface; @@ -157,7 +159,7 @@ class EntityImporter $entities = $this->serializer->deserialize($data, $options['class'].'[]', $options['format'], [ 'groups' => $groups, - 'csv_delimiter' => $options['csv_separator'], + 'csv_delimiter' => $options['csv_delimiter'], ]); //Ensure we have an array of entity elements. @@ -175,17 +177,16 @@ class EntityImporter if ($entity instanceof AbstractStructuralDBElement) { $entity->setParent($options['parent']); } + if ($entity instanceof Part && $options['part_category']) { + $entity->setCategory($options['part_category']); + } } //Validate the entities $errors = []; //Iterate over each $entity write it to DB. - foreach ($entities as $entity) { - /** @var AbstractStructuralDBElement $entity */ - //Move every imported entity to the selected parent - $entity->setParent($options['parent']); - + foreach ($entities as $key => $entity) { //Validate entity $tmp = $this->validator->validate($entity); @@ -196,6 +197,9 @@ class EntityImporter 'violations' => $tmp, 'entity' => $entity, ]; + + //Remove the invalid entity from the array + unset($entities[$key]); } } @@ -205,14 +209,21 @@ class EntityImporter protected function configureOptions(OptionsResolver $resolver): OptionsResolver { $resolver->setDefaults([ - 'csv_separator' => ';', - 'format' => 'json', + 'csv_delimiter' => ';', //The separator to use when importing csv files + 'format' => 'json', //The format of the file that should be imported 'class' => AbstractNamedDBElement::class, 'preserve_children' => true, 'parent' => null, //The parent element to which the imported elements should be added 'abort_on_validation_error' => true, + 'part_category' => null ]); + $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml']); + $resolver->setAllowedTypes('csv_delimiter', 'string'); + $resolver->setAllowedTypes('preserve_children', 'bool'); + $resolver->setAllowedTypes('class', 'string'); + $resolver->setAllowedTypes('part_category', [Category::class, 'null']); + return $resolver; } @@ -222,11 +233,12 @@ class EntityImporter * * @param File $file the file that should be used for importing * @param array $options options for the import process + * @param AbstractNamedDBElement[] $entities The imported entities are returned in this array * * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, * if an error happened during validation. When everything was successfully, the array should be empty. */ - public function importFileAndPersistToDB(File $file, array $options = []): array + public function importFileAndPersistToDB(File $file, array $options = [], array &$entities = []): array { $options = $this->configureOptions(new OptionsResolver())->resolve($options); @@ -238,15 +250,9 @@ class EntityImporter return $errors; } - //Iterate over each $entity write it to DB. + //Iterate over each $entity write it to DB (the invalid entities were already filtered out). foreach ($entities as $entity) { - //Validate entity - $tmp = $this->validator->validate($entity); - - //When no validation error occurred, persist entity to database (cascade must be set in entity) - if (null === $tmp) { - $this->em->persist($entity); - } + $this->em->persist($entity); } //Save changes to database, when no error happened, or we should continue on error. diff --git a/templates/main_card.html.twig b/templates/main_card.html.twig index 8bcd42ce..9eb80a02 100644 --- a/templates/main_card.html.twig +++ b/templates/main_card.html.twig @@ -1,6 +1,8 @@ {% extends "base.html.twig" %} {% block content %} + {% block before_card %}{% endblock %} +