Skip to content

Commit 48c70c3

Browse files
committed
Added way to batch edit the location of parts with a single stock
1 parent 68124a3 commit 48c70c3

File tree

5 files changed

+120
-36
lines changed

5 files changed

+120
-36
lines changed

src/Controller/PartListsController.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use App\Entity\Parts\Category;
3030
use App\Entity\Parts\Footprint;
3131
use App\Entity\Parts\Manufacturer;
32+
use App\Entity\Parts\Part;
3233
use App\Entity\Parts\StorageLocation;
3334
use App\Entity\Parts\Supplier;
3435
use App\Exceptions\InvalidRegexException;
@@ -43,8 +44,11 @@
4344
use Symfony\Component\HttpFoundation\Request;
4445
use Symfony\Component\HttpFoundation\Response;
4546
use Symfony\Component\Routing\Attribute\Route;
47+
use Symfony\Component\Translation\TranslatableMessage;
4648
use Symfony\Contracts\Translation\TranslatorInterface;
4749

50+
use function Symfony\Component\Translation\t;
51+
4852
class PartListsController extends AbstractController
4953
{
5054
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator)
@@ -71,13 +75,32 @@ public function tableAction(Request $request, PartsTableActionHandler $actionHan
7175
if (null === $action || null === $ids) {
7276
$this->addFlash('error', 'part.table.actions.no_params_given');
7377
} else {
78+
$errors = [];
79+
7480
$parts = $actionHandler->idStringToArray($ids);
75-
$redirectResponse = $actionHandler->handleAction($action, $parts, $target ? (int) $target : null, $redirect);
81+
$redirectResponse = $actionHandler->handleAction($action, $parts, $target ? (int) $target : null, $redirect, $errors);
7682

7783
//Save changes
7884
$this->entityManager->flush();
7985

80-
$this->addFlash('success', 'part.table.actions.success');
86+
if (count($errors) === 0) {
87+
$this->addFlash('success', 'part.table.actions.success');
88+
} else {
89+
$this->addFlash('error', t('part.table.actions.error', ['%count%' => count($errors)]));
90+
//Create a flash message for each error
91+
foreach ($errors as $error) {
92+
/** @var Part $part */
93+
$part = $error['part'];
94+
95+
$this->addFlash('error',
96+
t('part.table.actions.error_detail', [
97+
'%part_name%' => $part->getName(),
98+
'%part_id%' => $part->getID(),
99+
'%message%' => $error['message']
100+
])
101+
);
102+
}
103+
}
81104
}
82105

83106
//If the action handler returned a response, we use it, otherwise we redirect back to the previous page.

src/Controller/SelectAPIController.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use App\Entity\Parts\Footprint;
3030
use App\Entity\Parts\Manufacturer;
3131
use App\Entity\Parts\MeasurementUnit;
32+
use App\Entity\Parts\StorageLocation;
3233
use App\Entity\ProjectSystem\Project;
3334
use App\Form\Type\Helper\StructuralEntityChoiceHelper;
3435
use App\Services\Trees\NodesListBuilder;
@@ -78,6 +79,12 @@ public function projects(): Response
7879
return $this->getResponseForClass(Project::class, false);
7980
}
8081

82+
#[Route(path: '/storage_location', name: 'select_storage_location')]
83+
public function locations(): Response
84+
{
85+
return $this->getResponseForClass(StorageLocation::class, true);
86+
}
87+
8188
#[Route(path: '/export_level', name: 'select_export_level')]
8289
public function exportLevel(): Response
8390
{

src/Services/Parts/PartsTableActionHandler.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*/
2323
namespace App\Services\Parts;
2424

25+
use App\Entity\Parts\StorageLocation;
2526
use Symfony\Bundle\SecurityBundle\Security;
2627
use App\Entity\Parts\Category;
2728
use App\Entity\Parts\Footprint;
@@ -35,6 +36,9 @@
3536
use Symfony\Component\HttpFoundation\RedirectResponse;
3637
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
3738
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
39+
use Symfony\Contracts\Translation\TranslatableInterface;
40+
41+
use function Symfony\Component\Translation\t;
3842

3943
final class PartsTableActionHandler
4044
{
@@ -61,8 +65,9 @@ public function idStringToArray(string $ids): array
6165
/**
6266
* @param Part[] $selected_parts
6367
* @return RedirectResponse|null Returns a redirect response if the user should be redirected to another page, otherwise null
68+
* @phpstan-param-out array<array{'part': Part, 'message': string|TranslatableInterface}> $errors
6469
*/
65-
public function handleAction(string $action, array $selected_parts, ?int $target_id, ?string $redirect_url = null): ?RedirectResponse
70+
public function handleAction(string $action, array $selected_parts, ?int $target_id, ?string $redirect_url = null, array &$errors = []): ?RedirectResponse
6671
{
6772
if ($action === 'add_to_project') {
6873
return new RedirectResponse(
@@ -161,6 +166,29 @@ public function handleAction(string $action, array $selected_parts, ?int $target
161166
$this->denyAccessUnlessGranted('@measurement_units.read');
162167
$part->setPartUnit(null === $target_id ? null : $this->entityManager->find(MeasurementUnit::class, $target_id));
163168
break;
169+
case 'change_location':
170+
$this->denyAccessUnlessGranted('@storelocations.read');
171+
//Retrieve the first part lot and set the location for it
172+
$part_lots = $part->getPartLots();
173+
if ($part_lots->count() > 0) {
174+
if ($part_lots->count() > 1) {
175+
$errors[] = [
176+
'part' => $part,
177+
'message' => t('parts.table.action_handler.error.part_lots_multiple'),
178+
];
179+
break;
180+
}
181+
182+
$part_lot = $part_lots->first();
183+
$part_lot->setStorageLocation(null === $target_id ? null : $this->entityManager->find(StorageLocation::class, $target_id));
184+
} else { //Create a new part lot if there are none
185+
$part_lot = new PartLot();
186+
$part_lot->setPart($part);
187+
$part_lot->setInstockUnknown(true); //We do not know how many parts are in stock, so we set it to true
188+
$part_lot->setStorageLocation(null === $target_id ? null : $this->entityManager->find(StorageLocation::class, $target_id));
189+
$this->entityManager->persist($part_lot);
190+
}
191+
break;
164192

165193
default:
166194
throw new InvalidArgumentException('The given action is unknown! ('.$action.')');

templates/components/datatables.macro.html.twig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="change_footprint" data-url="{{ path('select_footprint') }}">{% trans %}part_list.action.action.change_footprint{% endtrans %}</option>
5555
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="change_manufacturer" data-url="{{ path('select_manufacturer') }}">{% trans %}part_list.action.action.change_manufacturer{% endtrans %}</option>
5656
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="change_unit" data-url="{{ path('select_measurement_unit') }}">{% trans %}part_list.action.action.change_unit{% endtrans %}</option>
57+
<option {% if not is_granted('@parts.edit') %}disabled{% endif %} value="change_location" data-url="{{ path('select_storage_location') }}">{% trans %}part_list.action.action.change_location{% endtrans %}</option>
5758
</optgroup>
5859
<optgroup label="{% trans %}part_list.action.group.labels{% endtrans %}">
5960
<option {% if not is_granted('@labels.create_labels') %}disabled{% endif %} value="generate_label_lot" data-url="{{ path('select_label_profiles_lot')}}">{% trans %}part_list.action.projects.generate_label_lot{% endtrans %}</option>

0 commit comments

Comments
 (0)