From 23e4b00e7710d2b6ff6eafb290d50f05ba4dd89b Mon Sep 17 00:00:00 2001 From: Marcel Diegelmann Date: Fri, 11 Apr 2025 14:00:11 +0200 Subject: [PATCH] =?UTF-8?q?JSON=20Importer=20mit=20Minimaldaten=20weiteren?= =?UTF-8?q?twickeln.=20Validierung=20mit=20Violations=20einf=C3=BChren=20u?= =?UTF-8?q?nd=20beim=20Import-Versuch=20zus=C3=A4tzlich=20mit=20ausgeben?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller/AssemblyController.php | 28 +- .../ImportExportSystem/BOMImporter.php | 317 ++++++++++++++---- .../ImportExportSystem/ImporterResult.php | 60 ++++ templates/assemblies/import_bom.html.twig | 25 +- translations/validators.cs.xlf | 54 +++ translations/validators.da.xlf | 54 +++ translations/validators.de.xlf | 56 +++- translations/validators.el.xlf | 54 +++ translations/validators.en.xlf | 54 +++ translations/validators.fr.xlf | 56 +++- translations/validators.hr.xlf | 54 +++ translations/validators.it.xlf | 54 +++ translations/validators.ja.xlf | 54 +++ translations/validators.pl.xlf | 54 +++ translations/validators.ru.xlf | 54 +++ translations/validators.zh.xlf | 54 +++ 16 files changed, 984 insertions(+), 98 deletions(-) create mode 100644 src/Services/ImportExportSystem/ImporterResult.php diff --git a/src/Controller/AssemblyController.php b/src/Controller/AssemblyController.php index 9710e9be..54cc1abb 100644 --- a/src/Controller/AssemblyController.php +++ b/src/Controller/AssemblyController.php @@ -29,7 +29,6 @@ use App\Entity\Parts\Part; use App\Form\AssemblySystem\AssemblyAddPartsType; use App\Form\AssemblySystem\AssemblyBuildType; use App\Helpers\Assemblies\AssemblyBuildRequest; -use App\Repository\PartRepository; use App\Services\ImportExportSystem\BOMImporter; use App\Services\AssemblySystem\AssemblyBuildHelper; use Doctrine\Common\Collections\ArrayCollection; @@ -52,14 +51,10 @@ use function Symfony\Component\Translation\t; #[Route(path: '/assembly')] class AssemblyController extends AbstractController { - private PartRepository $partRepository; - public function __construct( private readonly DataTableFactory $dataTableFactory, - private readonly EntityManagerInterface $entityManager, private readonly TranslatorInterface $translator, ) { - $this->partRepository = $this->entityManager->getRepository(Part::class); } #[Route(path: '/{id}/info', name: 'assembly_info', requirements: ['id' => '\d+'])] @@ -161,15 +156,14 @@ class AssemblyController extends AbstractController $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - - //Clear existing BOM entries if requested + // Clear existing entries if requested if ($form->get('clear_existing_bom')->getData()) { $assembly->getBomEntries()->clear(); $entityManager->flush(); } try { - $entries = $BOMImporter->importFileIntoAssembly($form->get('file')->getData(), $assembly, [ + $importerResult = $BOMImporter->importFileIntoAssembly($form->get('file')->getData(), $assembly, [ 'type' => $form->get('type')->getData(), ]); @@ -177,24 +171,17 @@ class AssemblyController extends AbstractController $errors = $validator->validateProperty($assembly, 'bom_entries'); //If no validation errors occured, save the changes and redirect to edit page - if (count ($errors) === 0) { - foreach ($entries as $entry) { - if ($entry instanceof AssemblyBOMEntry && $entry->getPart() !== null) { - $part = $entry->getPart(); - if ($part->getID() === null) { - $this->partRepository->save($part); - } - } - } + if (count ($errors) === 0 && $importerResult->getViolations()->count() === 0) { + $entries = $importerResult->getBomEntries(); $this->addFlash('success', t('assembly.bom_import.flash.success', ['%count%' => count($entries)])); $entityManager->flush(); + return $this->redirectToRoute('assembly_edit', ['id' => $assembly->getID()]); } - //When we get here, there were validation errors + //Show validation errors $this->addFlash('error', t('assembly.bom_import.flash.invalid_entries')); - } catch (\UnexpectedValueException|\RuntimeException|SyntaxError $e) { $this->addFlash('error', t('assembly.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()])); } @@ -226,7 +213,8 @@ class AssemblyController extends AbstractController 'assembly' => $assembly, 'jsonTemplate' => $jsonTemplate, 'form' => $form, - 'errors' => $errors ?? null, + 'validationErrors' => $errors ?? null, + 'importerErrors' => isset($importerResult) ? $importerResult->getViolations() : null, ]); } diff --git a/src/Services/ImportExportSystem/BOMImporter.php b/src/Services/ImportExportSystem/BOMImporter.php index 47ba90c9..6688780a 100644 --- a/src/Services/ImportExportSystem/BOMImporter.php +++ b/src/Services/ImportExportSystem/BOMImporter.php @@ -39,8 +39,9 @@ use League\Csv\Reader; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\OptionsResolver\OptionsResolver; -use RuntimeException; +use Symfony\Contracts\Translation\TranslatorInterface; use UnexpectedValueException; +use Symfony\Component\Validator\ConstraintViolation; /** * @see \App\Tests\Services\ImportExportSystem\BOMImporterTest @@ -57,16 +58,21 @@ class BOMImporter 5 => 'Supplier and ref', ]; - private readonly PartRepository $partRepository; + private string $jsonRoot = ''; - private readonly ManufacturerRepository $manufacturerRepository; + private PartRepository $partRepository; - private readonly CategoryRepository $categoryRepository; + private ManufacturerRepository $manufacturerRepository; - private readonly DBElementRepository $assemblyBOMEntryRepository; + private CategoryRepository $categoryRepository; + + private DBElementRepository $assemblyBOMEntryRepository; + + private TranslatorInterface $translator; public function __construct( private readonly EntityManagerInterface $entityManager, + private readonly TranslatorInterface $translator, private readonly LoggerInterface $logger, private readonly BOMValidationService $validationService ) { @@ -74,6 +80,7 @@ class BOMImporter $this->manufacturerRepository = $entityManager->getRepository(Manufacturer::class); $this->categoryRepository = $entityManager->getRepository(Category::class); $this->assemblyBOMEntryRepository = $entityManager->getRepository(AssemblyBOMEntry::class); + $this->translator = $translator; } protected function configureOptions(OptionsResolver $resolver): OptionsResolver @@ -110,20 +117,21 @@ class BOMImporter } /** - * Converts the given file into an array of BOM entries using the given options and save them into the given assembly. + * Converts the given file into an ImporterResult with an array of BOM entries using the given options and save them into the given assembly. * The changes are not saved into the database yet. - * @return AssemblyBOMEntry[] */ - public function importFileIntoAssembly(File $file, Assembly $assembly, array $options): array + public function importFileIntoAssembly(File $file, Assembly $assembly, array $options): ImporterResult { - $bomEntries = $this->fileToBOMEntries($file, $options, AssemblyBOMEntry::class); + $importerResult = $this->fileToImporterResult($file, $options, AssemblyBOMEntry::class); - //Assign the bom_entries to the assembly - foreach ($bomEntries as $bom_entry) { - $assembly->addBomEntry($bom_entry); + if ($importerResult->getViolations()->count() === 0) { + //Assign the bom_entries to the assembly + foreach ($importerResult->getBomEntries() as $bomEntry) { + $assembly->addBomEntry($bomEntry); + } } - return $bomEntries; + return $importerResult; } /** @@ -152,6 +160,14 @@ class BOMImporter }; } + /** + * Converts the given file into an ImporterResult with an array of BOM entries using the given options. + */ + public function fileToImporterResult(File $file, array $options, string $objectType = ProjectBOMEntry::class): ImporterResult + { + return $this->stringToImporterResult($file->getContent(), $options, $objectType); + } + /** * Import string data into an array of BOM entries, which are not yet assigned to a project. * @param string $data The data to import @@ -164,6 +180,24 @@ class BOMImporter $resolver = $this->configureOptions($resolver); $options = $resolver->resolve($options); + return match ($options['type']) { + 'kicad_pcbnew' => $this->parseKiCADPCB($data, $options, $objectType)->getBomEntries(), + default => throw new InvalidArgumentException('Invalid import type!'), + }; + } + + /** + * Import string data into an array of BOM entries, which are not yet assigned to a project. + * @param string $data The data to import + * @param array $options An array of options + * @return ProjectBOMEntry[]|AssemblyBOMEntry[] An array of imported entries + */ + public function stringToImporterResult(string $data, array $options, string $objectType = ProjectBOMEntry::class): ImporterResult + { + $resolver = new OptionsResolver(); + $resolver = $this->configureOptions($resolver); + $options = $resolver->resolve($options); + return match ($options['type']) { 'kicad_pcbnew' => $this->parseKiCADPCB($data, $objectType), 'json' => $this->parseJson($data, $options, $objectType), @@ -171,14 +205,14 @@ class BOMImporter }; } - private function parseKiCADPCB(string $data, string $objectType = ProjectBOMEntry::class): array + private function parseKiCADPCB(string $data, string $objectType = ProjectBOMEntry::class): ImporterResult { + $result = new ImporterResult(); + $csv = Reader::createFromString($data); $csv->setDelimiter(';'); $csv->setHeaderOffset(0); - $bom_entries = []; - foreach ($csv->getRecords() as $offset => $entry) { //Translate the german field names to english $entry = $this->normalizeColumnNames($entry); @@ -208,10 +242,10 @@ class BOMImporter $bom_entry->setComment($entry['Supplier and ref'] ?? ''); $bom_entry->setQuantity((float) ($entry['Quantity'] ?? 1)); - $bom_entries[] = $bom_entry; + $result->addBomEntry($bom_entry); } - return $bom_entries; + return $result; } /** @@ -271,30 +305,47 @@ class BOMImporter return $this->validationService->validateBOMEntries($mapped_entries, $options); } - private function parseJson(string $data, array $options = [], string $objectType = ProjectBOMEntry::class): array + private function parseJson(string $data, array $options = [], string $objectType = ProjectBOMEntry::class): ImporterResult { - $result = []; + $result = new ImporterResult(); + $this->jsonRoot = 'JSON Import for '.$objectType === ProjectBOMEntry::class ? 'Project' : 'Assembly'; $data = json_decode($data, true); - foreach ($data as $entry) { + foreach ($data as $key => $entry) { // Check quantity if (!isset($entry['quantity'])) { - throw new UnexpectedValueException('quantity missing'); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.quantity.required', + "entry[$key].quantity" + )); } - if (!is_float($entry['quantity']) || $entry['quantity'] <= 0) { - throw new UnexpectedValueException('quantity expected as float greater than 0.0'); + + if (isset($entry['quantity']) && (!is_float($entry['quantity']) || $entry['quantity'] <= 0)) { + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.quantity.float', + "entry[$key].quantity", + $entry['quantity'] + )); } // Check name if (isset($entry['name']) && !is_string($entry['name'])) { - throw new UnexpectedValueException('name of part list entry expected as string'); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.string.notEmpty', + "entry[$key].name", + $entry['name'] + )); } // Check if part is assigned with relevant information if (isset($entry['part'])) { if (!is_array($entry['part'])) { - throw new UnexpectedValueException('The property "part" should be an array'); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.array', + "entry[$key].part", + $entry['part'] + )); } $partIdValid = isset($entry['part']['id']) && is_int($entry['part']['id']) && $entry['part']['id'] > 0; @@ -303,9 +354,12 @@ class BOMImporter $partIpnValid = isset($entry['part']['ipn']) && is_string($entry['part']['ipn']) && trim($entry['part']['ipn']) !== ''; if (!$partIdValid && !$partNameValid && !$partMpnrValid && !$partIpnValid) { - throw new UnexpectedValueException( - 'The property "part" must have either assigned: "id" as integer greater than 0, "name", "mpnr", or "ipn" as non-empty string' - ); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.subproperties', + "entry[$key].part", + $entry['part'], + ['%propertyString%' => '"id", "name", "mpnr", or "ipn"'] + )); } $part = $partIdValid ? $this->partRepository->findOneBy(['id' => $entry['part']['id']]) : null; @@ -314,28 +368,71 @@ class BOMImporter $part = $part ?? ($partNameValid ? $this->partRepository->findOneBy(['name' => trim($entry['part']['name'])]) : null); if ($part === null) { - $part = new Part(); - $part->setName($entry['part']['name']); + $value = sprintf('part.id: %s, part.mpnr: %s, part.ipn: %s, part.name: %s', + isset($entry['part']['id']) ? '' . $entry['part']['id'] . '' : '-', + isset($entry['part']['mpnr']) ? '' . $entry['part']['mpnr'] . '' : '-', + isset($entry['part']['ipn']) ? '' . $entry['part']['ipn'] . '' : '-', + isset($entry['part']['name']) ? '' . $entry['part']['name'] . '' : '-', + ); + + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.notFoundFor', + "entry[$key].part", + $entry['part'], + ['%value%' => $value] + )); } - if ($partNameValid && $part->getName() !== trim($entry['part']['name'])) { - throw new RuntimeException(sprintf('Part name does not match exact the given name. Given for import: %s, found part: %s', $entry['part']['name'], $part->getName())); + if ($partNameValid && $part !== null && isset($entry['part']['name']) && $part->getName() !== trim($entry['part']['name'])) { + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.noExactMatch', + "entry[$key].part.name", + $entry['part']['name'], + [ + '%importValue%' => '' . $entry['part']['name'] . '', + '%foundId%' => $part->getID(), + '%foundValue%' => '' . $part->getName() . '' + ] + )); } - if ($partIpnValid && $part->getManufacturerProductNumber() !== trim($entry['part']['mpnr'])) { - throw new RuntimeException(sprintf('Part mpnr does not match exact the given mpnr. Given for import: %s, found part: %s', $entry['part']['mpnr'], $part->getManufacturerProductNumber())); + if ($partMpnrValid && $part !== null && isset($entry['part']['mpnr']) && $part->getManufacturerProductNumber() !== trim($entry['part']['mpnr'])) { + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.noExactMatch', + "entry[$key].part.mpnr", + $entry['part']['mpnr'], + [ + '%importValue%' => '' . $entry['part']['mpnr'] . '', + '%foundId%' => $part->getID(), + '%foundValue%' => '' . $part->getManufacturerProductNumber() . '' + ] + )); } - if ($partIpnValid && $part->getIpn() !== trim($entry['part']['ipn'])) { - throw new RuntimeException(sprintf('Part ipn does not match exact the given ipn. Given for import: %s, found part: %s', $entry['part']['ipn'], $part->getIpn())); + if ($partIpnValid && $part !== null && isset($entry['part']['ipn']) && $part->getIpn() !== trim($entry['part']['ipn'])) { + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.noExactMatch', + "entry[$key].part.ipn", + $entry['part']['ipn'], + [ + '%importValue%' => '' . $entry['part']['ipn'] . '', + '%foundId%' => $part->getID(), + '%foundValue%' => '' . $part->getIpn() . '' + ] + )); } // Part: Description check - if (isset($entry['part']['description']) && !is_null($entry['part']['description'])) { + if (isset($entry['part']['description'])) { if (!is_string($entry['part']['description']) || trim($entry['part']['description']) === '') { - throw new UnexpectedValueException('The property path "part.description" must be a non-empty string if not null'); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.string.notEmpty', + 'entry[$key].part.description', + $entry['part']['description'] + )); } } + $partDescription = $entry['part']['description'] ?? ''; // Part: Manufacturer check @@ -343,7 +440,11 @@ class BOMImporter $manufacturerNameValid = false; if (array_key_exists('manufacturer', $entry['part'])) { if (!is_array($entry['part']['manufacturer'])) { - throw new UnexpectedValueException('The property path "part.manufacturer" must be an array'); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.array', + 'entry[$key].part.manufacturer', + $entry['part']['manufacturer']) ?? null + ); } $manufacturerIdValid = isset($entry['part']['manufacturer']['id']) && is_int($entry['part']['manufacturer']['id']) && $entry['part']['manufacturer']['id'] > 0; @@ -351,23 +452,43 @@ class BOMImporter // Stellen sicher, dass mindestens eine Bedingung für manufacturer erfüllt sein muss if (!$manufacturerIdValid && !$manufacturerNameValid) { - throw new UnexpectedValueException( - 'The property "manufacturer" must have either assigned: "id" as integer greater than 0, or "name" as non-empty string' - ); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties', + "entry[$key].part.manufacturer", + $entry['part']['manufacturer'], + )); } } $manufacturer = $manufacturerIdValid ? $this->manufacturerRepository->findOneBy(['id' => $entry['part']['manufacturer']['id']]) : null; $manufacturer = $manufacturer ?? ($manufacturerNameValid ? $this->manufacturerRepository->findOneBy(['name' => trim($entry['part']['manufacturer']['name'])]) : null); - if ($manufacturer === null) { - throw new RuntimeException( - 'Manufacturer not found' + if (($manufacturerIdValid || $manufacturerNameValid) && $manufacturer === null) { + $value = sprintf( + 'manufacturer.id: %s, manufacturer.name: %s', + isset($entry['part']['manufacturer']['id']) && $entry['part']['manufacturer']['id'] !== null ? '' . $entry['part']['manufacturer']['id'] . '' : '-', + isset($entry['part']['manufacturer']['name']) && $entry['part']['manufacturer']['name'] !== null ? '' . $entry['part']['manufacturer']['name'] . '' : '-' ); + + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.notFoundFor', + "entry[$key].part.manufacturer", + $entry['part']['manufacturer'], + ['%value%' => $value] + )); } - if ($manufacturerNameValid && $manufacturer->getName() !== trim($entry['part']['manufacturer']['name'])) { - throw new RuntimeException(sprintf('Manufacturer name does not match exact the given name. Given for import: %s, found manufacturer: %s', $entry['manufacturer']['name'], $manufacturer->getName())); + if ($manufacturerNameValid && $manufacturer !== null && isset($entry['part']['manufacturer']['name']) && $manufacturer->getName() !== trim($entry['part']['manufacturer']['name'])) { + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.noExactMatch', + "entry[$key].part.manufacturer.name", + $entry['part']['manufacturer']['name'], + [ + '%importValue%' => '' . $entry['part']['manufacturer']['name'] . '', + '%foundId%' => $manufacturer->getID(), + '%foundValue%' => '' . $manufacturer->getName() . '' + ] + )); } // Part: Category check @@ -375,49 +496,82 @@ class BOMImporter $categoryNameValid = false; if (array_key_exists('category', $entry['part'])) { if (!is_array($entry['part']['category'])) { - throw new UnexpectedValueException('part.category must be an array'); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.array', + 'entry[$key].part.category', + $entry['part']['category']) ?? null + ); } $categoryIdValid = isset($entry['part']['category']['id']) && is_int($entry['part']['category']['id']) && $entry['part']['category']['id'] > 0; $categoryNameValid = isset($entry['part']['category']['name']) && is_string($entry['part']['category']['name']) && trim($entry['part']['category']['name']) !== ''; if (!$categoryIdValid && !$categoryNameValid) { - throw new UnexpectedValueException( - 'The property "category" must have either assigned: "id" as integer greater than 0, or "name" as non-empty string' - ); + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties', + "entry[$key].part.category", + $entry['part']['category'] + )); } } $category = $categoryIdValid ? $this->categoryRepository->findOneBy(['id' => $entry['part']['category']['id']]) : null; $category = $category ?? ($categoryNameValid ? $this->categoryRepository->findOneBy(['name' => trim($entry['part']['category']['name'])]) : null); - if ($category === null) { - throw new RuntimeException( - 'Category not found' + if (($categoryIdValid || $categoryNameValid) && $category === null) { + $value = sprintf( + 'category.id: %s, category.name: %s', + isset($entry['part']['category']['id']) && $entry['part']['category']['id'] !== null ? '' . $entry['part']['category']['id'] . '' : '-', + isset($entry['part']['category']['name']) && $entry['part']['category']['name'] !== null ? '' . $entry['part']['category']['name'] . '' : '-' ); + + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.notFoundFor', + "entry[$key].part.category", + $entry['part']['category'], + ['%value%' => $value] + )); } - if ($categoryNameValid && $category->getName() !== trim($entry['part']['category']['name'])) { - throw new RuntimeException(sprintf('Category name does not match exact the given name. Given for import: %s, found category: %s', $entry['category']['name'], $category->getName())); + if ($categoryNameValid && $category !== null && isset($entry['part']['category']['name']) && $category->getName() !== trim($entry['part']['category']['name'])) { + $result->addViolation($this->buildJsonViolation( + 'validator.bom_importer.json.parameter.noExactMatch', + "entry[$key].part.category.name", + $entry['part']['category']['name'], + [ + '%importValue%' => '' . $entry['part']['category']['name'] . '', + '%foundId%' => $category->getID(), + '%foundValue%' => '' . $category->getName() . '' + ] + )); } - $part->setDescription($partDescription); - $part->setManufacturer($manufacturer); - $part->setCategory($category); - - if ($partMpnrValid) { - $part->setManufacturerProductNumber($entry['part']['mpnr'] ?? ''); + if ($result->getViolations()->count() > 0) { + continue; } - if ($partIpnValid) { - $part->setIpn($entry['part']['ipn'] ?? ''); + + if ($partDescription !== '') { + //Beim Import / Aktualisieren von zugehörigen Bauteilen zu einer Baugruppe die Beschreibung des Bauteils mit übernehmen. + $part->setDescription($partDescription); + } + + if ($manufacturer !== null && $manufacturer->getID() !== $part->getManufacturerID()) { + //Beim Import / Aktualisieren von zugehörigen Bauteilen zu einer Baugruppe des Hersteller des Bauteils mit übernehmen. + $part->setManufacturer($manufacturer); + } + + if ($category !== null && $category->getID() !== $part->getCategoryID()) { + //Beim Import / Aktualisieren von zugehörigen Bauteilen zu einer Baugruppe die Kategorie des Bauteils mit übernehmen. + $part->setCategory($category); } if ($objectType === AssemblyBOMEntry::class) { $bomEntry = $this->assemblyBOMEntryRepository->findOneBy(['part' => $part]); if ($bomEntry === null) { - $name = isset($entry['name']) && $entry['name'] !== null ? trim($entry['name']) : ''; - $bomEntry = $this->assemblyBOMEntryRepository->findOneBy(['name' => $name]); + if (isset($entry['name']) && $entry['name'] !== '') { + $bomEntry = $this->assemblyBOMEntryRepository->findOneBy(['name' => $entry['name']]); + } if ($bomEntry === null) { $bomEntry = new AssemblyBOMEntry(); @@ -431,9 +585,22 @@ class BOMImporter $bomEntry->setName($entry['name'] ?? ''); $bomEntry->setPart($part); - } - $result[] = $bomEntry; + $result->addBomEntry($bomEntry); + } else { + //Eintrag ohne Part-Relation in die Bauteilliste aufnehmen + + if ($objectType === AssemblyBOMEntry::class) { + $bomEntry = new AssemblyBOMEntry(); + } else { + $bomEntry = new ProjectBOMEntry(); + } + + $bomEntry->setQuantity($entry['quantity']); + $bomEntry->setName($entry['name'] ?? ''); + + $result->addBomEntry($bomEntry); + } } return $result; @@ -462,6 +629,18 @@ class BOMImporter return $out; } + private function buildJsonViolation(string $message, string $propertyPath, mixed $invalidValue = null, array $parameters = []): ConstraintViolation + { + return new ConstraintViolation( + message: $this->translator->trans($message, $parameters, 'validators'), + messageTemplate: $message, + parameters: $parameters, + root: $this->jsonRoot, + propertyPath: $propertyPath, + invalidValue: $invalidValue + ); + } + /** * Parse KiCad schematic BOM with flexible field mapping */ diff --git a/src/Services/ImportExportSystem/ImporterResult.php b/src/Services/ImportExportSystem/ImporterResult.php new file mode 100644 index 00000000..4e289d13 --- /dev/null +++ b/src/Services/ImportExportSystem/ImporterResult.php @@ -0,0 +1,60 @@ +bomEntries = $bomEntries; + $this->violations = new ConstraintViolationList(); + } + + /** + * Fügt einen neuen BOM-Eintrag hinzu. + */ + public function addBomEntry(object $bomEntry): void + { + $this->bomEntries[] = $bomEntry; + } + + /** + * Gibt alle BOM-Einträge zurück. + */ + public function getBomEntries(): array + { + return $this->bomEntries; + } + + /** + * Gibt die Liste der Violation zurück. + */ + public function getViolations(): ConstraintViolationList + { + return $this->violations; + } + + /** + * Fügt eine neue `ConstraintViolation` zur Liste hinzu. + */ + public function addViolation(ConstraintViolation $violation): void + { + $this->violations->add($violation); + } + + /** + * Prüft, ob die Liste der Violationen leer ist. + */ + public function hasViolations(): bool + { + return count($this->violations) > 0; + } +} \ No newline at end of file diff --git a/templates/assemblies/import_bom.html.twig b/templates/assemblies/import_bom.html.twig index dc943042..04ff328a 100644 --- a/templates/assemblies/import_bom.html.twig +++ b/templates/assemblies/import_bom.html.twig @@ -3,16 +3,27 @@ {% block title %}{% trans %}assembly.import_bom{% endtrans %}{% endblock %} {% block before_card %} - {% if errors %} + {% if validationErrors or importerErrors %}

{% trans %}parts.import.errors.title{% endtrans %}

{% endif %} diff --git a/translations/validators.cs.xlf b/translations/validators.cs.xlf index 06354533..46471d02 100644 --- a/translations/validators.cs.xlf +++ b/translations/validators.cs.xlf @@ -389,5 +389,59 @@ Musíte vybrat součást nebo nastavit název pro nesoučást! + + + validator.bom_importer.json.quantity.required + Musíte zadat množství > 0! + + + + + validator.bom_importer.json.quantity.float + očekává se jako float větší než 0,0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + očekává se jako neprázdný řetězec + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + očekává se jako neprázdný řetězec nebo null + + + + + validator.bom_importer.json.parameter.array + očekává se jako pole (array) + + + + + validator.bom_importer.json.parameter.subproperties + musí mít alespoň jeden z následujících pod-parametrů: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + nenalezeno pro %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + se přesně neshoduje. Pro import zadáno: %importValue%, nalezeno (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + musí obsahovat jako pod-parametr buď: "id" jako celé číslo větší než 0 nebo "name" jako neprázdný řetězec + + diff --git a/translations/validators.da.xlf b/translations/validators.da.xlf index 9a9dea4c..88ec6784 100644 --- a/translations/validators.da.xlf +++ b/translations/validators.da.xlf @@ -365,5 +365,59 @@ Du skal vælge en del eller sætte et navn for en ikke-del! + + + validator.bom_importer.json.quantity.required + Du skal angive en mængde > 0! + + + + + validator.bom_importer.json.quantity.float + forventet som en float større end 0,0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + forventet som en ikke-tom streng + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + forventet som en ikke-tom streng eller null + + + + + validator.bom_importer.json.parameter.array + forventet som en array + + + + + validator.bom_importer.json.parameter.subproperties + skal have mindst én af følgende underparametre: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + ikke fundet for %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + stemmer ikke helt overens. Givet til import: %importValue%, fundet (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + skal indeholde som en underparameter enten: "id" som et heltal større end 0 eller "name" som en ikke-tom streng + + diff --git a/translations/validators.de.xlf b/translations/validators.de.xlf index 203382c8..6e03e870 100644 --- a/translations/validators.de.xlf +++ b/translations/validators.de.xlf @@ -386,7 +386,61 @@ validator.assembly.bom_entry.name_or_part_needed - Sie müssen ein Bauteil auswählen, oder einen Namen für ein nicht-Bauteil setzen! + Sie müssen ein Bauteil auswählen, oder einen Namen für den Eintrag setzen! + + + + + validator.bom_importer.json.quantity.required + Sie müssen eine Stückzahl > 0 angeben! + + + + + validator.bom_importer.json.quantity.float + wird als float größer als 0.0 erwartet + + + + + validator.bom_importer.json.parameter.string.notEmpty + als nicht leere Zeichenkette erwartet + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + als nicht leere Zeichenkette oder null erwartet + + + + + validator.bom_importer.json.parameter.array + als array erwartet + + + + + validator.bom_importer.json.parameter.subproperties + muss mindestens eines der folgenden Unter-Parameter haben: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + nicht gefunden für %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + stimmt nicht genau überein. Für den Import gegeben: %importValue%, gefunden (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + muss entweder als Unter-Parameter zugewiesen haben: "id" als Ganzzahl größer als 0 oder "name" als nicht leere Zeichenfolge diff --git a/translations/validators.el.xlf b/translations/validators.el.xlf index ee27863c..318ba892 100644 --- a/translations/validators.el.xlf +++ b/translations/validators.el.xlf @@ -31,5 +31,59 @@ Πρέπει να επιλέξετε ένα εξάρτημα ή να βάλετε ένα όνομα για ένα μη εξάρτημα! + + + validator.bom_importer.json.quantity.required + Πρέπει να εισαγάγετε ποσότητα > 0! + + + + + validator.bom_importer.json.quantity.float + αναμένεται ως δεκαδικός αριθμός (float) μεγαλύτερος από 0,0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + αναμένεται ως μη κενή συμβολοσειρά + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + αναμένεται ως μη κενή συμβολοσειρά ή null + + + + + validator.bom_importer.json.parameter.array + αναμένεται ως array + + + + + validator.bom_importer.json.parameter.subproperties + πρέπει να έχει τουλάχιστον μία από τις ακόλουθες υπο-παραμέτρους: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + δεν βρέθηκε για %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + δεν ταιριάζει απόλυτα. Δόθηκε για εισαγωγή: %importValue%, βρέθηκε (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + πρέπει να περιέχει ως υπο-παράμετρο είτε: "id" ως ακέραιο αριθμό μεγαλύτερο από 0 είτε "name" ως μη κενή συμβολοσειρά + + diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index 86525b6a..93640fff 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -389,5 +389,59 @@ __validator.assembly.bom_entry.name_or_part_needed + + + validator.bom_importer.json.quantity.required + you must specify a quantity > 0! + + + + + validator.bom_importer.json.quantity.float + expected as float greater than 0.0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + expected as non-empty string + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + als nicht leere Zeichenkette oder null erwartet + + + + + validator.bom_importer.json.parameter.array + expectd as array + + + + + validator.bom_importer.json.parameter.subproperties + must have at least one of the following sub-properties: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + not found for %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + does not match exactly. Given for import: %importValue%, found (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + must have either assigned as sub-property: "id" as an integer greater than 0, or "name" as a non-empty string + + diff --git a/translations/validators.fr.xlf b/translations/validators.fr.xlf index e9bf3259..cde3672f 100644 --- a/translations/validators.fr.xlf +++ b/translations/validators.fr.xlf @@ -224,7 +224,61 @@ validator.assembly.bom_entry.name_or_part_needed - Vous devez sélectionner une pièce ou attribuer un nom pour un non-élément ! + Vous devez sélectionner une pièce ou attribuer un nom pour un non-élément! + + + + + validator.bom_importer.json.quantity.required + Vous devez entrer une quantité > 0 ! + + + + + validator.bom_importer.json.quantity.float + attendu comme un nombre décimal (float) supérieur à 0,0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + attendu comme une chaîne de caractères non vide + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + attendu comme une chaîne de caractères non vide ou null + + + + + validator.bom_importer.json.parameter.array + attendu comme un tableau (array) + + + + + validator.bom_importer.json.parameter.subproperties + doit contenir au moins l'un des sous-paramètres suivants : %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + non trouvé pour %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + ne correspond pas exactement. Donné pour l'importation : %importValue%, trouvé (%foundId%) : %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + doit contenir comme sous-paramètre soit : "id" comme entier supérieur à 0 ou "name" comme chaîne de caractères non vide diff --git a/translations/validators.hr.xlf b/translations/validators.hr.xlf index 9c9c3960..98872420 100644 --- a/translations/validators.hr.xlf +++ b/translations/validators.hr.xlf @@ -383,5 +383,59 @@ Morate odabrati dio ili unijeti naziv za nedio! + + + validator.bom_importer.json.quantity.required + Morate unijeti količinu > 0! + + + + + validator.bom_importer.json.quantity.float + očekuje se decimalni broj (float) veći od 0,0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + očekuje se kao neprazan niz znakova + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + očekuje se kao neprazan niz znakova ili null + + + + + validator.bom_importer.json.parameter.array + očekuje se kao niz + + + + + validator.bom_importer.json.parameter.subproperties + mora sadržavati barem jedan od sljedećih pod-parametara: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + nije pronađeno za %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + ne podudara se točno. Uneseno za uvoz: %importValue%, pronađeno (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + mora sadržavati kao pod-parametar bilo: "id" kao cijeli broj veći od 0 ili "name" kao neprazan niz znakova + + diff --git a/translations/validators.it.xlf b/translations/validators.it.xlf index 2f747bc5..63ca86d7 100644 --- a/translations/validators.it.xlf +++ b/translations/validators.it.xlf @@ -383,5 +383,59 @@ È necessario selezionare una parte o inserire un nome per un non-parte! + + + validator.bom_importer.json.quantity.required + Devi inserire una quantità > 0! + + + + + validator.bom_importer.json.quantity.float + atteso come numero decimale (float) maggiore di 0,0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + atteso come stringa non vuota + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + atteso come stringa non vuota o null + + + + + validator.bom_importer.json.parameter.array + atteso come array + + + + + validator.bom_importer.json.parameter.subproperties + deve avere almeno uno dei seguenti sotto-parametri: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + non trovato per %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + non corrisponde esattamente. Valore dato per l'importazione: %importValue%, trovato (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + deve contenere come sotto-parametro: "id" come intero maggiore di 0 o "name" come stringa non vuota + + diff --git a/translations/validators.ja.xlf b/translations/validators.ja.xlf index 0156ffef..c0b54117 100644 --- a/translations/validators.ja.xlf +++ b/translations/validators.ja.xlf @@ -227,5 +227,59 @@ 部品を選択するか、非部品の名前を入力する必要があります! + + + validator.bom_importer.json.quantity.required + 数量 > 0 を入力する必要があります! + + + + + validator.bom_importer.json.quantity.float + 0.0 より大きい小数 (float) である必要があります + + + + + validator.bom_importer.json.parameter.string.notEmpty + 空でない文字列が期待されます + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + 空でない文字列または null が期待されます + + + + + validator.bom_importer.json.parameter.array + 配列として期待されます + + + + + validator.bom_importer.json.parameter.subproperties + 以下のサブパラメーターのいずれかを含む必要があります:%propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + %value% に対する項目が見つかりません + + + + + validator.bom_importer.json.parameter.noExactMatch + 完全には一致しません。インポートされた値:%importValue%、見つかった値 (%foundId%):%foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + サブパラメーターとして次のいずれかを含む必要があります:"id" は 0 より大きい整数、または "name" は空でない文字列 + + diff --git a/translations/validators.pl.xlf b/translations/validators.pl.xlf index 2cc4aef4..d417757d 100644 --- a/translations/validators.pl.xlf +++ b/translations/validators.pl.xlf @@ -383,5 +383,59 @@ Musisz wybrać element lub przypisać nazwę dla elementu niestandardowego! + + + validator.bom_importer.json.quantity.required + Musisz wprowadzić ilość > 0! + + + + + validator.bom_importer.json.quantity.float + oczekiwano liczby zmiennoprzecinkowej (float) większej od 0,0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + oczekiwano jako niepusty ciąg znaków + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + oczekiwano jako niepusty ciąg znaków lub null + + + + + validator.bom_importer.json.parameter.array + oczekiwano jako tablicę + + + + + validator.bom_importer.json.parameter.subproperties + musi zawierać co najmniej jeden z następujących podparametrów: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + nie znaleziono dla %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + brak dokładnego dopasowania. Wprowadzone do importu: %importValue%, znalezione (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + musi zawierać jako podparametr: "id" jako liczbę całkowitą większą od 0 lub "name" jako niepusty ciąg znaków + + diff --git a/translations/validators.ru.xlf b/translations/validators.ru.xlf index 4049b453..625aea24 100644 --- a/translations/validators.ru.xlf +++ b/translations/validators.ru.xlf @@ -383,5 +383,59 @@ Необходимо выбрать деталь или ввести название для недетали! + + + validator.bom_importer.json.quantity.required + Необходимо указать количество > 0! + + + + + validator.bom_importer.json.quantity.float + ожидается число с плавающей запятой (float), большее 0,0 + + + + + validator.bom_importer.json.parameter.string.notEmpty + ожидается непустая строка + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + ожидается непустая строка или null + + + + + validator.bom_importer.json.parameter.array + ожидается массив + + + + + validator.bom_importer.json.parameter.subproperties + должен содержать хотя бы один из следующих под-параметров: %propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + не найдено для %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + точное совпадение отсутствует. Указано для импорта: %importValue%, найдено (%foundId%): %foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + должен содержать под-параметр: "id" как целое число больше 0 или "name" как непустая строка + + diff --git a/translations/validators.zh.xlf b/translations/validators.zh.xlf index 3eab5c4e..5093ce98 100644 --- a/translations/validators.zh.xlf +++ b/translations/validators.zh.xlf @@ -371,5 +371,59 @@ 必须选择零件或为非零件指定名称! + + + validator.bom_importer.json.quantity.required + 必须输入数量 > 0! + + + + + validator.bom_importer.json.quantity.float + 应为大于 0.0 的浮点数 (float) + + + + + validator.bom_importer.json.parameter.string.notEmpty + 应为非空字符串 + + + + + validator.bom_importer.json.parameter.string.notEmpty.null + 应为非空字符串或 null + + + + + validator.bom_importer.json.parameter.array + 应为数组 + + + + + validator.bom_importer.json.parameter.subproperties + 必须包含以下子参数之一:%propertyString% + + + + + validator.bom_importer.json.parameter.notFoundFor + 未找到对应值 %value% + + + + + validator.bom_importer.json.parameter.noExactMatch + 未精确匹配。用于导入的值:%importValue%,找到的值 (%foundId%):%foundValue% + + + + + validator.bom_importer.json.parameter.manufacturerOrCategoryWithSubProperties + 必须包含子参数:"id" 为大于 0 的整数,或 "name" 为非空字符串 + +