Basic multi-build implementation.

This commit is contained in:
Dave Branton 2026-05-22 14:48:54 +12:00
parent ad0c60f766
commit 4af3cdb7ab
6 changed files with 208 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,66 @@ class ToolsController extends AbstractController
]);
}
#[Route(path: '/multi_build', name: 'tools_multi_build')]
public function multiBuild(LoggerInterface $logger, 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;
if ($form->isSubmitted())
{
if ($form->isValid())
{
$submitted = "YES";
foreach($all_projects as $p)
{
$count_for_project = $form->get($p->getID() . "_project")->getData() + 0;
$logger->Info("QTY", ['count_for_project'=>$count_for_project, 'name'=>$p->getName()]);
if ($count_for_project > 0)
{
$logger->Info("BOMSIZE", ['bomsize'=>count($p->getBomEntries())]);
foreach ($p->getBomEntries() as $bom_entry)
{
$part_id = $bom_entry->getPart()->getID();
$logger->Info("ISAPART", ['q'=>$bom_entry->getQuantity(), 'name'=>$bom_entry->getPart()->getName()]);
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());
}
}
}
}
foreach($combined_bom as $cb)
{
$total_instock = $cb['part']->getAmountSum();
$logger->Info("COMBINED BOM", ['total_instock'=>$total_instock, 'needed'=>$cb['quantity']]);
if ($total_instock < $cb['quantity'])
{
$needs_ordering[] = array('needed'=>($cb['quantity']-$total_instock), 'part'=>$cb['part']);
$can_build = false;
}
}
}
}
return $this->render('tools/multi_build/multi_build.html.twig', [
'form'=>$form,
'needs_ordering'=>$needs_ordering,
'can_build'=>$can_build
]);
}
#[Route(path: '/ic_logos', name: 'tools_ic_logos')]
public function icLogos(): Response
{

View file

@ -0,0 +1,68 @@
<?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\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use App\Entity\ProjectSystem\Project;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Psr\Log\LoggerInterface;
class ProjectMultiBuildType extends AbstractType
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$this->logger->info("LEN {len}", ['len'=>count($options['projects'])]);
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

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 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\Helpers\Projects;
use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot;
use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Validator\Constraints\ProjectSystem\ValidProjectBuildRequest;
/**
* @see \App\Tests\Helpers\Projects\ProjectBuildRequestTest
*/
#[ValidProjectBuildRequest]
final class ProjectBuildRequest
{
}

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,28 @@
{% 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> {% trans %}project.builds.build_not_possible{% endtrans %}</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>
{{ form(form) }}
{% endblock %}