2022-12-18 21:58:21 +01:00
|
|
|
<?php
|
|
|
|
/*
|
|
|
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
|
|
|
*
|
|
|
|
* Copyright (C) 2019 - 2022 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\Controller;
|
|
|
|
|
|
|
|
use App\DataTables\ProjectBomEntriesDataTable;
|
2022-12-28 23:32:46 +01:00
|
|
|
use App\Entity\Parts\Part;
|
2022-12-18 21:58:21 +01:00
|
|
|
use App\Entity\ProjectSystem\Project;
|
2022-12-28 23:32:46 +01:00
|
|
|
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
|
|
|
use App\Form\ProjectSystem\ProjectBOMEntryCollectionType;
|
2023-01-22 00:01:16 +01:00
|
|
|
use App\Form\ProjectSystem\ProjectBuildType;
|
2022-12-28 23:32:46 +01:00
|
|
|
use App\Form\Type\StructuralEntityType;
|
2023-01-22 00:01:16 +01:00
|
|
|
use App\Helpers\Projects\ProjectBuildRequest;
|
2023-03-16 00:05:46 +01:00
|
|
|
use App\Services\ImportExportSystem\BOMImporter;
|
2023-01-18 23:07:51 +01:00
|
|
|
use App\Services\ProjectSystem\ProjectBuildHelper;
|
2022-12-28 23:32:46 +01:00
|
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
2023-03-16 23:32:12 +01:00
|
|
|
use League\Csv\SyntaxError;
|
2022-12-18 21:58:21 +01:00
|
|
|
use Omines\DataTablesBundle\DataTableFactory;
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
2023-03-16 00:05:46 +01:00
|
|
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
|
|
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
|
|
|
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
2022-12-28 23:32:46 +01:00
|
|
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
2022-12-18 21:58:21 +01:00
|
|
|
use Symfony\Component\HttpFoundation\Request;
|
2022-12-28 23:32:46 +01:00
|
|
|
use Symfony\Component\HttpFoundation\Response;
|
2022-12-18 21:58:21 +01:00
|
|
|
use Symfony\Component\Routing\Annotation\Route;
|
2022-12-29 13:15:26 +01:00
|
|
|
use Symfony\Component\Validator\Constraints\NotNull;
|
2023-03-16 00:05:46 +01:00
|
|
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|
|
|
|
|
|
|
use function Symfony\Component\Translation\t;
|
2022-12-18 21:58:21 +01:00
|
|
|
|
2023-05-28 01:21:05 +02:00
|
|
|
#[Route(path: '/project')]
|
2022-12-18 21:58:21 +01:00
|
|
|
class ProjectController extends AbstractController
|
|
|
|
{
|
2023-06-11 14:15:46 +02:00
|
|
|
public function __construct(private readonly DataTableFactory $dataTableFactory)
|
2022-12-18 21:58:21 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-05-28 01:21:05 +02:00
|
|
|
#[Route(path: '/{id}/info', name: 'project_info', requirements: ['id' => '\d+'])]
|
2023-04-15 19:33:39 +02:00
|
|
|
public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper): Response
|
2022-12-18 21:58:21 +01:00
|
|
|
{
|
|
|
|
$this->denyAccessUnlessGranted('read', $project);
|
|
|
|
|
|
|
|
$table = $this->dataTableFactory->createFromType(ProjectBomEntriesDataTable::class, ['project' => $project])
|
|
|
|
->handleRequest($request);
|
|
|
|
|
|
|
|
if ($table->isCallback()) {
|
|
|
|
return $table->getResponse();
|
|
|
|
}
|
|
|
|
|
2023-02-04 23:03:32 +01:00
|
|
|
return $this->render('projects/info/info.html.twig', [
|
2023-01-18 23:07:51 +01:00
|
|
|
'buildHelper' => $buildHelper,
|
2022-12-18 21:58:21 +01:00
|
|
|
'datatable' => $table,
|
|
|
|
'project' => $project,
|
|
|
|
]);
|
|
|
|
}
|
2022-12-28 23:32:46 +01:00
|
|
|
|
2023-05-28 01:21:05 +02:00
|
|
|
#[Route(path: '/{id}/build', name: 'project_build', requirements: ['id' => '\d+'])]
|
2023-01-22 16:59:58 +01:00
|
|
|
public function build(Project $project, Request $request, ProjectBuildHelper $buildHelper, EntityManagerInterface $entityManager): Response
|
2023-01-21 21:41:08 +01:00
|
|
|
{
|
|
|
|
$this->denyAccessUnlessGranted('read', $project);
|
|
|
|
|
|
|
|
//If no number of builds is given (or it is invalid), just assume 1
|
|
|
|
$number_of_builds = $request->query->getInt('n', 1);
|
|
|
|
if ($number_of_builds < 1) {
|
|
|
|
$number_of_builds = 1;
|
|
|
|
}
|
|
|
|
|
2023-01-22 00:01:16 +01:00
|
|
|
$projectBuildRequest = new ProjectBuildRequest($project, $number_of_builds);
|
|
|
|
$form = $this->createForm(ProjectBuildType::class, $projectBuildRequest);
|
2023-01-21 21:41:08 +01:00
|
|
|
|
2023-01-22 00:01:16 +01:00
|
|
|
$form->handleRequest($request);
|
2023-01-22 16:59:58 +01:00
|
|
|
if ($form->isSubmitted()) {
|
|
|
|
if ($form->isValid()) {
|
2023-01-22 23:40:10 +01:00
|
|
|
//Ensure that the user can withdraw stock from all parts
|
|
|
|
$this->denyAccessUnlessGranted('@parts_stock.withdraw');
|
|
|
|
|
2023-01-22 23:27:45 +01:00
|
|
|
//We have to do a flush already here, so that the newly created partLot gets an ID and can be logged to DB later.
|
|
|
|
$entityManager->flush();
|
|
|
|
$buildHelper->doBuild($projectBuildRequest);
|
2023-01-22 16:59:58 +01:00
|
|
|
$entityManager->flush();
|
|
|
|
$this->addFlash('success', 'project.build.flash.success');
|
|
|
|
|
|
|
|
return $this->redirect(
|
|
|
|
$request->get('_redirect',
|
|
|
|
$this->generateUrl('project_info', ['id' => $project->getID()]
|
|
|
|
)));
|
|
|
|
}
|
2023-04-15 22:05:29 +02:00
|
|
|
|
|
|
|
$this->addFlash('error', 'project.build.flash.invalid_input');
|
2023-01-22 00:01:16 +01:00
|
|
|
}
|
|
|
|
|
2023-05-28 01:21:05 +02:00
|
|
|
return $this->render('projects/build/build.html.twig', [
|
2023-01-21 21:41:08 +01:00
|
|
|
'buildHelper' => $buildHelper,
|
|
|
|
'project' => $project,
|
2023-01-22 00:01:16 +01:00
|
|
|
'build_request' => $projectBuildRequest,
|
2023-01-21 21:41:08 +01:00
|
|
|
'number_of_builds' => $number_of_builds,
|
2023-01-22 00:01:16 +01:00
|
|
|
'form' => $form,
|
2023-01-21 21:41:08 +01:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2023-05-28 01:21:05 +02:00
|
|
|
#[Route(path: '/{id}/import_bom', name: 'project_import_bom', requirements: ['id' => '\d+'])]
|
2023-03-16 00:05:46 +01:00
|
|
|
public function importBOM(Request $request, EntityManagerInterface $entityManager, Project $project,
|
|
|
|
BOMImporter $BOMImporter, ValidatorInterface $validator): Response
|
|
|
|
{
|
|
|
|
$this->denyAccessUnlessGranted('edit', $project);
|
|
|
|
|
|
|
|
$builder = $this->createFormBuilder();
|
|
|
|
$builder->add('file', FileType::class, [
|
|
|
|
'label' => 'import.file',
|
|
|
|
'required' => true,
|
|
|
|
'attr' => [
|
|
|
|
'accept' => '.csv'
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
$builder->add('type', ChoiceType::class, [
|
|
|
|
'label' => 'project.bom_import.type',
|
|
|
|
'required' => true,
|
|
|
|
'choices' => [
|
|
|
|
'project.bom_import.type.kicad_pcbnew' => 'kicad_pcbnew',
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
$builder->add('clear_existing_bom', CheckboxType::class, [
|
|
|
|
'label' => 'project.bom_import.clear_existing_bom',
|
|
|
|
'required' => false,
|
|
|
|
'data' => false,
|
|
|
|
'help' => 'project.bom_import.clear_existing_bom.help',
|
|
|
|
]);
|
|
|
|
$builder->add('submit', SubmitType::class, [
|
|
|
|
'label' => 'import.btn',
|
|
|
|
]);
|
|
|
|
|
|
|
|
$form = $builder->getForm();
|
|
|
|
|
|
|
|
$form->handleRequest($request);
|
|
|
|
if ($form->isSubmitted() && $form->isValid()) {
|
|
|
|
|
|
|
|
//Clear existing BOM entries if requested
|
|
|
|
if ($form->get('clear_existing_bom')->getData()) {
|
|
|
|
$project->getBomEntries()->clear();
|
2023-03-17 00:11:01 +01:00
|
|
|
$entityManager->flush();
|
2023-03-16 00:05:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$entries = $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 occured, save the changes and redirect to edit page
|
|
|
|
if (count ($errors) === 0) {
|
|
|
|
$this->addFlash('success', t('project.bom_import.flash.success', ['%count%' => count($entries)]));
|
|
|
|
$entityManager->flush();
|
|
|
|
return $this->redirectToRoute('project_edit', ['id' => $project->getID()]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count ($errors) > 0) {
|
|
|
|
$this->addFlash('error', t('project.bom_import.flash.invalid_entries'));
|
|
|
|
}
|
2023-06-11 14:15:46 +02:00
|
|
|
} catch (\UnexpectedValueException|SyntaxError $e) {
|
2023-03-16 23:32:12 +01:00
|
|
|
$this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()]));
|
2023-03-16 00:05:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-28 01:21:05 +02:00
|
|
|
return $this->render('projects/import_bom.html.twig', [
|
2023-03-16 00:05:46 +01:00
|
|
|
'project' => $project,
|
|
|
|
'form' => $form,
|
|
|
|
'errors' => $errors ?? null,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2023-05-28 01:21:05 +02:00
|
|
|
#[Route(path: '/add_parts', name: 'project_add_parts_no_id')]
|
|
|
|
#[Route(path: '/{id}/add_parts', name: 'project_add_parts', requirements: ['id' => '\d+'])]
|
2022-12-29 13:15:26 +01:00
|
|
|
public function addPart(Request $request, EntityManagerInterface $entityManager, ?Project $project): Response
|
2022-12-28 23:32:46 +01:00
|
|
|
{
|
2023-06-11 14:55:06 +02:00
|
|
|
if($project instanceof Project) {
|
2022-12-29 13:15:26 +01:00
|
|
|
$this->denyAccessUnlessGranted('edit', $project);
|
|
|
|
} else {
|
2023-01-08 20:10:58 +01:00
|
|
|
$this->denyAccessUnlessGranted('@projects.edit');
|
2022-12-29 13:15:26 +01:00
|
|
|
}
|
2022-12-28 23:32:46 +01:00
|
|
|
|
|
|
|
$builder = $this->createFormBuilder();
|
|
|
|
$builder->add('project', StructuralEntityType::class, [
|
|
|
|
'class' => Project::class,
|
|
|
|
'required' => true,
|
2023-06-11 14:55:06 +02:00
|
|
|
'disabled' => $project instanceof Project, //If a project is given, disable the field
|
2022-12-28 23:32:46 +01:00
|
|
|
'data' => $project,
|
2022-12-29 13:15:26 +01:00
|
|
|
'constraints' => [
|
|
|
|
new NotNull()
|
|
|
|
]
|
2022-12-28 23:32:46 +01:00
|
|
|
]);
|
|
|
|
$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_data = new ArrayCollection();
|
2023-06-11 14:15:46 +02:00
|
|
|
foreach (explode(',', (string) $request->get('parts', '')) as $part_id) {
|
2022-12-28 23:32:46 +01:00
|
|
|
$part = $entityManager->getRepository(Part::class)->find($part_id);
|
|
|
|
if (null !== $part) {
|
|
|
|
//If there is already a BOM entry for this part, we use this one (we edit it then)
|
|
|
|
$bom_entry = $entityManager->getRepository(ProjectBOMEntry::class)->findOneBy([
|
|
|
|
'project' => $project,
|
|
|
|
'part' => $part
|
|
|
|
]);
|
|
|
|
if ($bom_entry) {
|
|
|
|
$preset_data->add($bom_entry);
|
|
|
|
} else { //Otherwise create an empty one
|
|
|
|
$entry = new ProjectBOMEntry();
|
|
|
|
$entry->setProject($project);
|
|
|
|
$entry->setPart($part);
|
|
|
|
$preset_data->add($entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$form['bom_entries']->setData($preset_data);
|
|
|
|
|
|
|
|
$form->handleRequest($request);
|
|
|
|
if ($form->isSubmitted() && $form->isValid()) {
|
2022-12-29 13:15:26 +01:00
|
|
|
$target_project = $project ?? $form->get('project')->getData();
|
|
|
|
|
|
|
|
//Ensure that we really have acces to the selected project
|
|
|
|
$this->denyAccessUnlessGranted('edit', $target_project);
|
|
|
|
|
2022-12-28 23:32:46 +01:00
|
|
|
$data = $form->getData();
|
|
|
|
$bom_entries = $data['bom_entries'];
|
|
|
|
foreach ($bom_entries as $bom_entry){
|
2022-12-29 13:15:26 +01:00
|
|
|
$target_project->addBOMEntry($bom_entry);
|
2022-12-28 23:32:46 +01:00
|
|
|
}
|
|
|
|
$entityManager->flush();
|
|
|
|
|
|
|
|
//If a redirect query parameter is set, redirect to this page
|
|
|
|
if ($request->query->get('_redirect')) {
|
|
|
|
return $this->redirect($request->query->get('_redirect'));
|
|
|
|
}
|
|
|
|
//Otherwise just show the project info page
|
2022-12-29 13:15:26 +01:00
|
|
|
return $this->redirectToRoute('project_info', ['id' => $target_project->getID()]);
|
2022-12-28 23:32:46 +01:00
|
|
|
}
|
|
|
|
|
2023-05-28 01:21:05 +02:00
|
|
|
return $this->render('projects/add_parts.html.twig', [
|
2022-12-28 23:32:46 +01:00
|
|
|
'project' => $project,
|
|
|
|
'form' => $form,
|
|
|
|
]);
|
|
|
|
}
|
2022-12-18 21:58:21 +01:00
|
|
|
}
|