Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/permissions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
label: "tools.builtin_footprints_viewer.title"
ic_logos:
label: "perm.tools.ic_logos"
multi_build:
label: "tools.multi_build.title"

info_providers:
label: "perm.part.info_providers"
Expand Down
90 changes: 90 additions & 0 deletions src/Controller/ToolsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@
use App\Services\System\UpdateAvailableFacade;
use App\Settings\AppSettings;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Runtime\SymfonyRuntime;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\ProjectSystem\Project;
use App\Form\ProjectSystem\ProjectMultiBuildType;
use Psr\Log\LoggerInterface;

#[Route(path: '/tools')]
class ToolsController extends AbstractController
Expand Down Expand Up @@ -122,6 +127,91 @@ public function builtInFootprintsViewer(BuiltinAttachmentsFinder $builtinAttachm
]);
}

#[Route(path: '/multi_build', name: 'tools_multi_build')]
public function multiBuild(EntityManagerInterface $entityManager, Request $request): Response
{
//$this->denyAccessUnlessGranted('@tools.multi_build');
$all_projects = $entityManager->getRepository(Project::class)->findAll();

$form = $this->createForm(ProjectMultiBuildType::class, [], ['projects'=>$all_projects]);

$form->handleRequest($request);
$combined_bom=[];
$needs_ordering = [];
$can_build = true;
$orders_per_supplier = null;
if ($form->isSubmitted())
{
if ($form->isValid())
{
foreach($all_projects as $p)
{
$count_for_project = $form->get($p->getID() . "_project")->getData() + 0;
if ($count_for_project > 0)
{
foreach ($p->getBomEntries() as $bom_entry)
{
if ($bom_entry->getPart())
{
$part_id = $bom_entry->getPart()->getID();
if (array_key_exists($part_id, $combined_bom))
{
$combined_bom[$part_id]['quantity'] += $bom_entry->getQuantity() * $count_for_project;
}
else
{
$combined_bom[$part_id] = array('quantity'=>$bom_entry->getQuantity() * $count_for_project, 'part'=>$bom_entry->getPart());
}
}
}
}
}

$orders_per_supplier=[];

foreach($combined_bom as $cb)
{
$total_instock = $cb['part']->getAmountSum();
if ($total_instock < $cb['quantity'])
{
$can_build = false;
$suppliers = $cb['part']->getOrderDetails();
if ($suppliers && count($suppliers) > 0)
{
$mid = $suppliers[0]->getSupplier()->getID();
$orderable_part=array(
'part'=>$cb['part'],
'pn'=>$suppliers[0]->getSupplierPartNr(),
'needed'=>($cb['quantity']-$total_instock),
'link'=>$suppliers[0]->getSupplierProductURL());
if (array_key_exists($mid, $orders_per_supplier))
{
$orders_per_supplier[$mid]['items'][] = $orderable_part;
}
else
{
$orders_per_supplier[$mid] = array(
'supplier'=>$suppliers[0]->getSupplier()->getName(),
'items'=>array($orderable_part));
}
}
else
{
$needs_ordering[] = array('needed'=>($cb['quantity']-$total_instock), 'part'=>$cb['part']);
}
}
}
}
}

return $this->render('tools/multi_build/multi_build.html.twig', [
'form'=>$form,
'needs_ordering'=>$needs_ordering,
'can_build'=>$can_build,
'orders_per_supplier'=>$orders_per_supplier
]);
}

#[Route(path: '/ic_logos', name: 'tools_ic_logos')]
public function icLogos(): Response
{
Expand Down
58 changes: 58 additions & 0 deletions src/Form/ProjectSystem/ProjectMultiBuildType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2026 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/>.
*/

declare(strict_types=1);


namespace App\Form\ProjectSystem;

use App\Services\InfoProviderSystem\ProviderRegistry;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use App\Entity\ProjectSystem\Project;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ProjectMultiBuildType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
foreach($options['projects'] as $p)
{
$builder->add($p->getID() . '_project', NumberType::class, [
'label' => $p->getName(),
'required' => false,
],0);
}

$builder->add('submit', SubmitType::class, [
'label' => 'info_providers.search.submit',
]);
}

public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'projects' => [],
]);
$resolver->setAllowedTypes('projects', 'array');
}
}
6 changes: 6 additions & 0 deletions src/Services/Trees/ToolsTreeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ protected function getToolsNode(): array
$this->urlGenerator->generate('tools_builtin_footprints_viewer')
))->setIcon('fa-treeview fa-fw fa-solid fa-images');
}
if ($this->security->isGranted('@tools.multi_build')) {
$nodes[] = (new TreeViewNode(
"Multi Build",
$this->urlGenerator->generate('tools_multi_build')
))->setIcon('fa-treeview fa-fw fa-solid fa-images');
}
if ($this->security->isGranted('@tools.ic_logos')) {
$nodes[] = (new TreeViewNode(
$this->translator->trans('perm.tools.ic_logos'),
Expand Down
63 changes: 63 additions & 0 deletions templates/tools/multi_build/multi_build.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{% extends "main_card.html.twig" %}

{% block title %}Multi Build{% endblock %}


{% block card_title %}<i class="fas fa-chart-bar fa-fw"></i>
Multi Build{% endblock %}

{% block card_body %}

<div class="alert {% if can_build %}alert-success{% else %}alert-danger{% endif %}" role="alert">
{% if not can_build %}
<h5><i class="fa-solid fa-circle-exclamation fa-fw"></i>Parts needed without supplier.</h5>
<ul>
{% for bom_entry in needs_ordering %}
<li>
<b><a href="{{ entity_url(bom_entry.part) }}">{{ bom_entry.part.name }}</a></b>
{{ bom_entry.needed }} Needed.
</li>
{% endfor %}
</ul>
{% endif %}
</div>

{% if orders_per_supplier is not null %}
<table class="table table-striped table-hover">
{% for supplier in orders_per_supplier %}

<thead>
<tr>
<th colspan=7>
{% if supplier.supplier is not null %}{{ supplier.supplier }}{% else %}No supplier{% endif %}
</th>
</tr>
<tr>
<th></th>
<th>{% trans %}name.label{% endtrans %}</th>
<th>{% trans %}orderdetails.edit.supplierpartnr{% endtrans %}</th>
<th>{% trans %}description.label{% endtrans %}</th>
<th>Quantity Needed</th>
<th></th>
</tr>
</thead>

{% for item in supplier.items %}
<tr>
<td/>
<td><a href="{{ entity_url(item.part) }}">{{ item.part.name }}</a></td>
<td><a href="{{ item.link }}">{{ item.pn }}</a></td>
<td>{{ item.part.description }}</td>
<td>{{ item.needed }}</td>
<td/>
</tr>
{% endfor %}

{% endfor %}

</table>
{% endif %}

{{ form(form) }}

{% endblock %}