This commit is contained in:
davebranton 2026-07-31 14:43:40 +02:00 committed by GitHub
commit 63a996d0cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 219 additions and 0 deletions

View file

@ -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"

View file

@ -31,9 +31,14 @@ use App\Services\System\GitVersionInfoProvider;
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
@ -122,6 +127,91 @@ class ToolsController extends AbstractController
]);
}
#[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
{

View file

@ -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');
}
}

View file

@ -131,6 +131,12 @@ class ToolsTreeBuilder
$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'),

View file

@ -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 %}