diff --git a/config/permissions.yaml b/config/permissions.yaml index 39e91b57e..1aa0c43e2 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -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" diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php index 76dffb4d0..a0795d44a 100644 --- a/src/Controller/ToolsController.php +++ b/src/Controller/ToolsController.php @@ -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 @@ -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 { diff --git a/src/Form/ProjectSystem/ProjectMultiBuildType.php b/src/Form/ProjectSystem/ProjectMultiBuildType.php new file mode 100644 index 000000000..ce7235a78 --- /dev/null +++ b/src/Form/ProjectSystem/ProjectMultiBuildType.php @@ -0,0 +1,58 @@ +. + */ + +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'); + } +} diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index 6397e3af1..df2fcb2ec 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -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'), diff --git a/templates/tools/multi_build/multi_build.html.twig b/templates/tools/multi_build/multi_build.html.twig new file mode 100644 index 000000000..9b3fe8f6d --- /dev/null +++ b/templates/tools/multi_build/multi_build.html.twig @@ -0,0 +1,63 @@ +{% extends "main_card.html.twig" %} + +{% block title %}Multi Build{% endblock %} + + +{% block card_title %} + Multi Build{% endblock %} + +{% block card_body %} + + + +{% if orders_per_supplier is not null %} + +{% for supplier in orders_per_supplier %} + + + + + + + + + + + + + + + + {% for item in supplier.items %} + + + + + + + {% endfor %} + +{% endfor %} + +
+ {% if supplier.supplier is not null %}{{ supplier.supplier }}{% else %}No supplier{% endif %} +
{% trans %}name.label{% endtrans %}{% trans %}orderdetails.edit.supplierpartnr{% endtrans %}{% trans %}description.label{% endtrans %}Quantity Needed
+ {{ item.part.name }}{{ item.pn }}{{ item.part.description }}{{ item.needed }} +
+{% endif %} + +{{ form(form) }} + +{% endblock %} \ No newline at end of file