Added a page to quickly add many parts to a project from parts lists.

This commit is contained in:
Jan Böhmer 2022-12-28 23:32:46 +01:00
parent 345fb0a3c1
commit 25494c9ddf
7 changed files with 157 additions and 3 deletions

View file

@ -77,7 +77,7 @@ class PartListsController extends AbstractController
$this->addFlash('error', 'part.table.actions.no_params_given'); $this->addFlash('error', 'part.table.actions.no_params_given');
} else { } else {
$parts = $actionHandler->idStringToArray($ids); $parts = $actionHandler->idStringToArray($ids);
$actionHandler->handleAction($action, $parts, $target ? (int) $target : null); $redirectResponse = $actionHandler->handleAction($action, $parts, $target ? (int) $target : null, $redirect);
//Save changes //Save changes
$this->entityManager->flush(); $this->entityManager->flush();
@ -85,6 +85,11 @@ class PartListsController extends AbstractController
$this->addFlash('success', 'part.table.actions.success'); $this->addFlash('success', 'part.table.actions.success');
} }
//If the action handler returned a response, we use it, otherwise we redirect back to the previous page.
if ($redirectResponse){
return $redirectResponse;
}
return $this->redirect($redirect); return $this->redirect($redirect);
} }

View file

@ -21,10 +21,18 @@
namespace App\Controller; namespace App\Controller;
use App\DataTables\ProjectBomEntriesDataTable; use App\DataTables\ProjectBomEntriesDataTable;
use App\Entity\Parts\Part;
use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Form\ProjectSystem\ProjectBOMEntryCollectionType;
use App\Form\Type\StructuralEntityType;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Omines\DataTablesBundle\DataTableFactory; use Omines\DataTablesBundle\DataTableFactory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
/** /**
@ -58,4 +66,69 @@ class ProjectController extends AbstractController
'project' => $project, 'project' => $project,
]); ]);
} }
/**
* @Route("/{id}/add_parts", name="project_add_parts")
* @param Request $request
* @param Project|null $project
*/
public function addPart(Request $request, Project $project, EntityManagerInterface $entityManager): Response
{
$this->denyAccessUnlessGranted('edit', $project);
$builder = $this->createFormBuilder();
$builder->add('project', StructuralEntityType::class, [
'class' => Project::class,
'required' => true,
'disabled' => true,
'data' => $project,
]);
$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();
foreach (explode(',', $request->get('parts', '')) as $part_id) {
$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()) {
$data = $form->getData();
$bom_entries = $data['bom_entries'];
foreach ($bom_entries as $bom_entry){
$project->addBOMEntry($bom_entry);
}
$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
return $this->redirectToRoute('project_info', ['id' => $project->getID()]);
}
return $this->renderForm('Projects/add_parts.html.twig', [
'project' => $project,
'form' => $form,
]);
}
} }

View file

@ -25,6 +25,7 @@ use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint; use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\MeasurementUnit;
use App\Entity\ProjectSystem\Project;
use App\Services\Trees\NodesListBuilder; use App\Services\Trees\NodesListBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -79,6 +80,14 @@ class SelectAPIController extends AbstractController
return $this->getResponseForClass(MeasurementUnit::class, true); return $this->getResponseForClass(MeasurementUnit::class, true);
} }
/**
* @Route("/project", name="select_project")
*/
public function projects(): Response
{
return $this->getResponseForClass(Project::class, false);
}
protected function getResponseForClass(string $class, bool $include_empty = false): Response protected function getResponseForClass(string $class, bool $include_empty = false): Response
{ {
$test_obj = new $class(); $test_obj = new $class();

View file

@ -29,6 +29,8 @@ use App\Repository\DBElementRepository;
use App\Repository\PartRepository; use App\Repository\PartRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@ -36,11 +38,13 @@ final class PartsTableActionHandler
{ {
private EntityManagerInterface $entityManager; private EntityManagerInterface $entityManager;
private Security $security; private Security $security;
private UrlGeneratorInterface $urlGenerator;
public function __construct(EntityManagerInterface $entityManager, Security $security) public function __construct(EntityManagerInterface $entityManager, Security $security, UrlGeneratorInterface $urlGenerator)
{ {
$this->entityManager = $entityManager; $this->entityManager = $entityManager;
$this->security = $security; $this->security = $security;
$this->urlGenerator = $urlGenerator;
} }
/** /**
@ -62,9 +66,21 @@ final class PartsTableActionHandler
/** /**
* @param Part[] $selected_parts * @param Part[] $selected_parts
* @return RedirectResponse|null Returns a redirect response if the user should be redirected to another page, otherwise null
*/ */
public function handleAction(string $action, array $selected_parts, ?int $target_id): void public function handleAction(string $action, array $selected_parts, ?int $target_id, ?string $redirect_url = null): ?RedirectResponse
{ {
if ($action === 'add_to_project') {
return new RedirectResponse(
$this->urlGenerator->generate('project_add_parts', [
'id' => $target_id,
'parts' => implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)),
'_redirect' => $redirect_url
])
);
}
//Iterate over the parts and apply the action to it: //Iterate over the parts and apply the action to it:
foreach ($selected_parts as $part) { foreach ($selected_parts as $part) {
if (!$part instanceof Part) { if (!$part instanceof Part) {
@ -115,6 +131,8 @@ final class PartsTableActionHandler
default: default:
throw new InvalidArgumentException('The given action is unknown! ('.$action.')'); throw new InvalidArgumentException('The given action is unknown! ('.$action.')');
} }
return null;
} }
} }

View file

@ -0,0 +1,22 @@
{% extends "main_card.html.twig" %}
{% block title %}{% trans %}project.add_parts_to_project{% endtrans %}{% endblock %}
{% block card_title %}
<i class="fa-solid fa-magnifying-glass-plus fa-fw"></i>
{% trans %}project.add_parts_to_project{% endtrans %}{% if project %}: <i>{{ project.name }}</i>{% endif %}
{% endblock %}
{% block card_content %}
{{ form_start(form) }}
{{ form_row(form.project) }}
{% form_theme form.bom_entries with ['Form/collection_types_layout.html.twig'] %}
{{ form_widget(form.bom_entries) }}
{{ form_row(form.submit) }}
{{ form_end(form) }}
{% endblock %}

View file

@ -48,6 +48,9 @@
<option {% if not is_granted('@manufacturers.read') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option> <option {% if not is_granted('@manufacturers.read') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option>
<option {% if not is_granted('@measurement_units.read') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option> <option {% if not is_granted('@measurement_units.read') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option>
</optgroup> </optgroup>
<optgroup label="{% trans %}part_list.action.group.projects{% endtrans %}">
<option {% if not is_granted('@devices.read') %}disabled{% endif %} value="add_to_project" data-url="{{ path('select_project')}}">{% trans %}part_list.action.projects.add_to_project{% endtrans %}</option>
</optgroup>
<option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option> <option {% if not is_granted('@parts.delete') %}disabled{% endif %} value="delete">{% trans %}part_list.action.action.delete{% endtrans %}</option>
</select> </select>

View file

@ -9983,5 +9983,29 @@ Element 3</target>
<target>Add entry</target> <target>Add entry</target>
</segment> </segment>
</unit> </unit>
<unit id="gwloIYB" name="part_list.action.group.projects">
<segment>
<source>part_list.action.group.projects</source>
<target>Projects</target>
</segment>
</unit>
<unit id="QDvlT74" name="part_list.action.projects.add_to_project">
<segment>
<source>part_list.action.projects.add_to_project</source>
<target>Add parts to project</target>
</segment>
</unit>
<unit id="ZVrMX6T" name="project.bom.delete.confirm">
<segment>
<source>project.bom.delete.confirm</source>
<target>Do you really want to delete this BOM entry?</target>
</segment>
</unit>
<unit id="T_wVWg_" name="project.add_parts_to_project">
<segment>
<source>project.add_parts_to_project</source>
<target>Add parts to project BOM</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>