Projekt-Importer um JSON/CSV Importer analog zu Assemblies erweitern

This commit is contained in:
Marcel Diegelmann 2025-06-24 09:42:12 +02:00
parent 2066d20edf
commit ecbc8b4e80
18 changed files with 3112 additions and 55 deletions

View file

@ -46,14 +46,16 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function Symfony\Component\Translation\t;
#[Route(path: '/project')]
class ProjectController extends AbstractController
{
public function __construct(private readonly DataTableFactory $dataTableFactory)
{
public function __construct(
private readonly DataTableFactory $dataTableFactory,
private readonly TranslatorInterface $translator,
) {
}
#[Route(path: '/{id}/info', name: 'project_info', requirements: ['id' => '\d+'])]
@ -147,6 +149,8 @@ class ProjectController extends AbstractController
'label' => 'project.bom_import.type',
'required' => true,
'choices' => [
'project.bom_import.type.json' => 'json',
'project.bom_import.type.csv' => 'csv',
'project.bom_import.type.kicad_pcbnew' => 'kicad_pcbnew',
'project.bom_import.type.kicad_schematic' => 'kicad_schematic',
'project.bom_import.type.generic_csv' => 'generic_csv',
@ -189,17 +193,20 @@ class ProjectController extends AbstractController
}
// For PCB imports, proceed directly
$entries = $BOMImporter->importFileIntoProject($form->get('file')->getData(), $project, [
'type' => $import_type,
$importerResult = $BOMImporter->importFileIntoProject($form->get('file')->getData(), $project, [
'type' => $form->get('type')->getData(),
]);
// Validate the project entries
$errors = $validator->validateProperty($project, 'bom_entries');
// If no validation errors occurred, save the changes and redirect to edit page
if (count($errors) === 0) {
//If no validation errors occured, save the changes and redirect to edit page
if (count ($errors) === 0 && $importerResult->getViolations()->count() === 0) {
$entries = $importerResult->getBomEntries();
$this->addFlash('success', t('project.bom_import.flash.success', ['%count%' => count($entries)]));
$entityManager->flush();
return $this->redirectToRoute('project_edit', ['id' => $project->getID()]);
}
@ -211,10 +218,29 @@ class ProjectController extends AbstractController
}
}
$jsonTemplate = [
[
"quantity" => 1.0,
"name" => $this->translator->trans('project.bom_import.template.entry.name'),
"part" => [
"id" => null,
"ipn" => $this->translator->trans('project.bom_import.template.entry.part.ipn'),
"mpnr" => $this->translator->trans('project.bom_import.template.entry.part.mpnr'),
"name" => $this->translator->trans('project.bom_import.template.entry.part.name'),
"manufacturer" => [
"id" => null,
"name" => $this->translator->trans('project.bom_import.template.entry.part.manufacturer.name')
],
]
]
];
return $this->render('projects/import_bom.html.twig', [
'project' => $project,
'jsonTemplate' => $jsonTemplate,
'form' => $form,
'errors' => $errors ?? null,
'validationErrors' => $errors ?? null,
'importerErrors' => isset($importerResult) ? $importerResult->getViolations() : null,
]);
}

View file

@ -36,6 +36,7 @@ use ApiPlatform\OpenApi\Model\Operation;
use ApiPlatform\Serializer\Filter\PropertyFilter;
use App\ApiPlatform\Filter\LikeFilter;
use App\Entity\Contracts\TimeStampableInterface;
use App\Repository\DBElementRepository;
use App\Validator\UniqueValidatableInterface;
use Doctrine\DBAL\Types\Types;
use App\Entity\Base\AbstractDBElement;
@ -54,7 +55,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
* The ProjectBOMEntry class represents an entry in a project's BOM.
*/
#[ORM\HasLifecycleCallbacks]
#[ORM\Entity]
#[ORM\Entity(repositoryClass: DBElementRepository::class)]
#[ORM\Table('project_bom_entries')]
#[ApiResource(
operations: [

View file

@ -87,7 +87,7 @@ class BOMImporter
$this->partRepository = $entityManager->getRepository(Part::class);
$this->manufacturerRepository = $entityManager->getRepository(Manufacturer::class);
$this->categoryRepository = $entityManager->getRepository(Category::class);
$this->projectBOMEntryRepository = $entityManager->getRepository(Project::class);
$this->projectBOMEntryRepository = $entityManager->getRepository(ProjectBOMEntry::class);
$this->assemblyBOMEntryRepository = $entityManager->getRepository(AssemblyBOMEntry::class);
$this->translator = $translator;
}
@ -111,18 +111,19 @@ class BOMImporter
/**
* Converts the given file into an array of BOM entries using the given options and save them into the given project.
* The changes are not saved into the database yet.
* @return ProjectBOMEntry[]
*/
public function importFileIntoProject(File $file, Project $project, array $options): array
public function importFileIntoProject(UploadedFile $file, Project $project, array $options): ImporterResult
{
$bom_entries = $this->fileToBOMEntries($file, $options);
$importerResult = $this->fileToImporterResult($file, $options);
//Assign the bom_entries to the project
foreach ($bom_entries as $bom_entry) {
$project->addBomEntry($bom_entry);
if ($importerResult->getViolations()->count() === 0) {
//Assign the bom_entries to the project
foreach ($importerResult->getBomEntries() as $bomEntry) {
$project->addBomEntry($bomEntry);
}
}
return $bom_entries;
return $importerResult;
}
/**
@ -202,7 +203,7 @@ class BOMImporter
$fileExtension,
[
'%extension%' => $fileExtension,
'%importType%' => $this->translator->trans('assembly.bom_import.type.'.$options['type']),
'%importType%' => $this->translator->trans($objectType === ProjectBOMEntry::class ? 'project.bom_import.type.'.$options['type'] : 'assembly.bom_import.type.'.$options['type']),
'%allowedExtensions%' => implode(', ', $validExtensions),
]
));
@ -711,7 +712,7 @@ class BOMImporter
$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 (($categoryIdValid || $categoryNameValid) && $category === null) {
if (($categoryIdValid || $categoryNameValid)) {
$value = sprintf(
'category.id: %s, category.name: %s',
isset($entry['part']['category']['id']) && $entry['part']['category']['id'] !== null ? '<strong>' . $entry['part']['category']['id'] . '</strong>' : '-',
@ -748,12 +749,12 @@ class BOMImporter
$part->setDescription($partDescription);
}
if ($manufacturer !== null && $manufacturer->getID() !== $part->getManufacturerID()) {
if ($manufacturer !== null && $manufacturer->getID() !== $part->getManufacturer()->getID()) {
//When updating the associated parts, take over to a assembly of the manufacturer of the part.
$part->setManufacturer($manufacturer);
}
if ($category !== null && $category->getID() !== $part->getCategoryID()) {
if ($category !== null && $category->getID() !== $part->getCategory()->getID()) {
//When updating the associated parts to a assembly, take over the category of the part.
$part->setCategory($category);
}
@ -771,11 +772,26 @@ class BOMImporter
}
}
} else {
$bomEntry = new ProjectBOMEntry();
$bomEntry = $this->projectBOMEntryRepository->findOneBy(['part' => $part]);
if ($bomEntry === null) {
if (isset($entry['name']) && $entry['name'] !== '') {
$bomEntry = $this->projectBOMEntryRepository->findOneBy(['name' => $entry['name']]);
}
if ($bomEntry === null) {
$bomEntry = new ProjectBOMEntry();
}
}
}
$bomEntry->setQuantity((float) $entry['quantity']);
$bomEntry->setName($entry['name'] ?? '');
if (isset($entry['name'])) {
$bomEntry->setName(trim($entry['name']) === '' ? null : trim ($entry['name']));
} else {
$bomEntry->setName(null);
}
$bomEntry->setPart($part);