mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-24 10:49:00 +02:00
Added a very basic import dialog for Parts
This commit is contained in:
parent
8f033910ce
commit
7a9b7c87a4
7 changed files with 149 additions and 22 deletions
|
@ -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());
|
||||
|
|
|
@ -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 ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue