mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-13 13:49:33 +00:00
Assemblies einführen
This commit is contained in:
parent
e1418dfdc1
commit
6fa960df42
107 changed files with 14101 additions and 96 deletions
64
src/Form/AdminPages/AssemblyAdminForm.php
Normal file
64
src/Form/AdminPages/AssemblyAdminForm.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 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\Form\AdminPages;
|
||||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Form\AssemblySystem\AssemblyBOMEntryCollectionType;
|
||||
use App\Form\Type\RichTextEditorType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class AssemblyAdminForm extends BaseEntityAdminForm
|
||||
{
|
||||
protected function additionalFormElements(FormBuilderInterface $builder, array $options, AbstractNamedDBElement $entity): void
|
||||
{
|
||||
$builder->add('description', RichTextEditorType::class, [
|
||||
'required' => false,
|
||||
'label' => 'part.edit.description',
|
||||
'mode' => 'markdown-single_line',
|
||||
'empty_data' => '',
|
||||
'attr' => [
|
||||
'placeholder' => 'part.edit.description.placeholder',
|
||||
'rows' => 2,
|
||||
],
|
||||
]);
|
||||
|
||||
$builder->add('bom_entries', AssemblyBOMEntryCollectionType::class);
|
||||
|
||||
$builder->add('status', ChoiceType::class, [
|
||||
'attr' => [
|
||||
'class' => 'form-select',
|
||||
],
|
||||
'label' => 'assembly.edit.status',
|
||||
'required' => false,
|
||||
'empty_data' => '',
|
||||
'choices' => [
|
||||
'assembly.status.draft' => 'draft',
|
||||
'assembly.status.planning' => 'planning',
|
||||
'assembly.status.in_production' => 'in_production',
|
||||
'assembly.status.finished' => 'finished',
|
||||
'assembly.status.archived' => 'archived',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\UserSystem\Group;
|
||||
|
|
@ -114,7 +115,7 @@ class BaseEntityAdminForm extends AbstractType
|
|||
);
|
||||
}
|
||||
|
||||
if ($entity instanceof AbstractStructuralDBElement && !($entity instanceof Group || $entity instanceof Project || $entity instanceof Currency)) {
|
||||
if ($entity instanceof AbstractStructuralDBElement && !($entity instanceof Group || $entity instanceof Project || $entity instanceof Assembly || $entity instanceof Currency)) {
|
||||
$builder->add('alternative_names', TextType::class, [
|
||||
'required' => false,
|
||||
'label' => 'entity.edit.alternative_names.label',
|
||||
|
|
|
|||
88
src/Form/AssemblySystem/AssemblyAddPartsType.php
Normal file
88
src/Form/AssemblySystem/AssemblyAddPartsType.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?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\Form\AssemblySystem;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\AssemblySystem\AssemblyBOMEntry;
|
||||
use App\Form\Type\StructuralEntityType;
|
||||
use App\Validator\Constraints\UniqueObjectCollection;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotNull;
|
||||
|
||||
class AssemblyAddPartsType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('assembly', StructuralEntityType::class, [
|
||||
'class' => Assembly::class,
|
||||
'required' => true,
|
||||
'disabled' => $options['assembly'] instanceof Assembly, //If a assembly is given, disable the field
|
||||
'data' => $options['assembly'],
|
||||
'constraints' => [
|
||||
new NotNull()
|
||||
]
|
||||
]);
|
||||
$builder->add('bom_entries', AssemblyBOMEntryCollectionType::class, [
|
||||
'entry_options' => [
|
||||
'constraints' => [
|
||||
new UniqueEntity(fields: ['part', 'assembly'], message: 'assembly.bom_entry.part_already_in_bom',
|
||||
entityClass: AssemblyBOMEntry::class),
|
||||
new UniqueEntity(fields: ['name', 'assembly'], message: 'assembly.bom_entry.name_already_in_bom',
|
||||
entityClass: AssemblyBOMEntry::class, ignoreNull: true),
|
||||
]
|
||||
],
|
||||
'constraints' => [
|
||||
new UniqueObjectCollection(message: 'assembly.bom_entry.part_already_in_bom', fields: ['part']),
|
||||
new UniqueObjectCollection(message: 'assembly.bom_entry.name_already_in_bom', fields: ['name']),
|
||||
]
|
||||
]);
|
||||
$builder->add('submit', SubmitType::class, ['label' => 'save']);
|
||||
|
||||
//After submit set the assembly for all bom entries, so that it can be validated properly
|
||||
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
|
||||
$form = $event->getForm();
|
||||
/** @var Assembly $assembly */
|
||||
$assembly = $form->get('assembly')->getData();
|
||||
$bom_entries = $form->get('bom_entries')->getData();
|
||||
|
||||
foreach ($bom_entries as $bom_entry) {
|
||||
$bom_entry->setAssembly($assembly);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'assembly' => null,
|
||||
]);
|
||||
|
||||
$resolver->setAllowedTypes('assembly', ['null', Assembly::class]);
|
||||
}
|
||||
}
|
||||
32
src/Form/AssemblySystem/AssemblyBOMEntryCollectionType.php
Normal file
32
src/Form/AssemblySystem/AssemblyBOMEntryCollectionType.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\AssemblySystem;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class AssemblyBOMEntryCollectionType extends AbstractType
|
||||
{
|
||||
public function getParent(): string
|
||||
{
|
||||
return CollectionType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'entry_type' => AssemblyBOMEntryType::class,
|
||||
'entry_options' => [
|
||||
'label' => false,
|
||||
],
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'by_reference' => false,
|
||||
'reindex_enable' => true,
|
||||
'label' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
90
src/Form/AssemblySystem/AssemblyBOMEntryType.php
Normal file
90
src/Form/AssemblySystem/AssemblyBOMEntryType.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\AssemblySystem;
|
||||
|
||||
use App\Entity\AssemblySystem\AssemblyBOMEntry;
|
||||
use App\Form\Type\BigDecimalNumberType;
|
||||
use App\Form\Type\CurrencyEntityType;
|
||||
use App\Form\Type\PartSelectType;
|
||||
use App\Form\Type\RichTextEditorType;
|
||||
use App\Form\Type\SIUnitType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Event\PreSetDataEvent;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class AssemblyBOMEntryType extends AbstractType
|
||||
{
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) {
|
||||
$form = $event->getForm();
|
||||
/** @var AssemblyBOMEntry $data */
|
||||
$data = $event->getData();
|
||||
|
||||
$form->add('quantity', SIUnitType::class, [
|
||||
'label' => 'assembly.bom.quantity',
|
||||
'measurement_unit' => $data && $data->getPart() ? $data->getPart()->getPartUnit() : null,
|
||||
]);
|
||||
});
|
||||
|
||||
$builder
|
||||
|
||||
->add('part', PartSelectType::class, [
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'assembly.bom.name',
|
||||
'required' => false,
|
||||
])
|
||||
->add('mountnames', TextType::class, [
|
||||
'required' => false,
|
||||
'label' => 'assembly.bom.mountnames',
|
||||
'empty_data' => '',
|
||||
'attr' => [
|
||||
'class' => 'tagsinput',
|
||||
'data-controller' => 'elements--tagsinput',
|
||||
]
|
||||
])
|
||||
->add('comment', RichTextEditorType::class, [
|
||||
'required' => false,
|
||||
'label' => 'assembly.bom.comment',
|
||||
'empty_data' => '',
|
||||
'mode' => 'markdown-single_line',
|
||||
'attr' => [
|
||||
'rows' => 2,
|
||||
],
|
||||
])
|
||||
->add('price', BigDecimalNumberType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
'scale' => 5,
|
||||
'html5' => true,
|
||||
'attr' => [
|
||||
'min' => 0,
|
||||
'step' => 'any',
|
||||
],
|
||||
])
|
||||
->add('priceCurrency', CurrencyEntityType::class, [
|
||||
'required' => false,
|
||||
'label' => false,
|
||||
'short' => true,
|
||||
])
|
||||
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AssemblyBOMEntry::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
183
src/Form/AssemblySystem/AssemblyBuildType.php
Normal file
183
src/Form/AssemblySystem/AssemblyBuildType.php
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<?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\Form\AssemblySystem;
|
||||
|
||||
use App\Helpers\Assemblies\AssemblyBuildRequest;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Form\Type\PartLotSelectType;
|
||||
use App\Form\Type\SIUnitType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\Event\PreSetDataEvent;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class AssemblyBuildType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
public function __construct(private readonly Security $security)
|
||||
{
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'compound' => true,
|
||||
'data_class' => AssemblyBuildRequest::class
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->setDataMapper($this);
|
||||
|
||||
$builder->add('submit', SubmitType::class, [
|
||||
'label' => 'assembly.build.btn_build',
|
||||
'disabled' => !$this->security->isGranted('@parts_stock.withdraw'),
|
||||
]);
|
||||
|
||||
$builder->add('dontCheckQuantity', CheckboxType::class, [
|
||||
'label' => 'assembly.build.dont_check_quantity',
|
||||
'help' => 'assembly.build.dont_check_quantity.help',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'data-controller' => 'pages--dont-check-quantity-checkbox'
|
||||
]
|
||||
]);
|
||||
|
||||
$builder->add('comment', TextType::class, [
|
||||
'label' => 'part.info.withdraw_modal.comment',
|
||||
'help' => 'part.info.withdraw_modal.comment.hint',
|
||||
'empty_data' => '',
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
|
||||
//The form is initially empty, we have to define the fields after we know the data
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) {
|
||||
$form = $event->getForm();
|
||||
/** @var AssemblyBuildRequest $build_request */
|
||||
$build_request = $event->getData();
|
||||
|
||||
$form->add('addBuildsToBuildsPart', CheckboxType::class, [
|
||||
'label' => 'assembly.build.add_builds_to_builds_part',
|
||||
'required' => false,
|
||||
'disabled' => !$build_request->getAssembly()->getBuildPart() instanceof Part,
|
||||
]);
|
||||
|
||||
if ($build_request->getAssembly()->getBuildPart() instanceof Part) {
|
||||
$form->add('buildsPartLot', PartLotSelectType::class, [
|
||||
'label' => 'assembly.build.builds_part_lot',
|
||||
'required' => false,
|
||||
'part' => $build_request->getAssembly()->getBuildPart(),
|
||||
'placeholder' => 'assembly.build.buildsPartLot.new_lot'
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($build_request->getPartBomEntries() as $bomEntry) {
|
||||
//Every part lot has a field to specify the number of parts to take from this lot
|
||||
foreach ($build_request->getPartLotsForBOMEntry($bomEntry) as $lot) {
|
||||
$form->add('lot_' . $lot->getID(), SIUnitType::class, [
|
||||
'label' => false,
|
||||
'measurement_unit' => $bomEntry->getPart()->getPartUnit(),
|
||||
'max' => min($build_request->getNeededAmountForBOMEntry($bomEntry), $lot->getAmount()),
|
||||
'disabled' => !$this->security->isGranted('withdraw', $lot),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public function mapDataToForms($data, \Traversable $forms): void
|
||||
{
|
||||
if (!$data instanceof AssemblyBuildRequest) {
|
||||
throw new \RuntimeException('Data must be an instance of ' . AssemblyBuildRequest::class);
|
||||
}
|
||||
|
||||
/** @var FormInterface[] $forms */
|
||||
$forms = iterator_to_array($forms);
|
||||
foreach ($forms as $key => $form) {
|
||||
//Extract the lot id from the form name
|
||||
$matches = [];
|
||||
if (preg_match('/^lot_(\d+)$/', $key, $matches)) {
|
||||
$lot_id = (int) $matches[1];
|
||||
$form->setData($data->getLotWithdrawAmount($lot_id));
|
||||
}
|
||||
}
|
||||
|
||||
$forms['comment']->setData($data->getComment());
|
||||
$forms['dontCheckQuantity']->setData($data->isDontCheckQuantity());
|
||||
$forms['addBuildsToBuildsPart']->setData($data->getAddBuildsToBuildsPart());
|
||||
if (isset($forms['buildsPartLot'])) {
|
||||
$forms['buildsPartLot']->setData($data->getBuildsPartLot());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function mapFormsToData(\Traversable $forms, &$data): void
|
||||
{
|
||||
if (!$data instanceof AssemblyBuildRequest) {
|
||||
throw new \RuntimeException('Data must be an instance of ' . AssemblyBuildRequest::class);
|
||||
}
|
||||
|
||||
/** @var FormInterface[] $forms */
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
foreach ($forms as $key => $form) {
|
||||
//Extract the lot id from the form name
|
||||
$matches = [];
|
||||
if (preg_match('/^lot_(\d+)$/', $key, $matches)) {
|
||||
$lot_id = (int) $matches[1];
|
||||
$data->setLotWithdrawAmount($lot_id, (float) $form->getData());
|
||||
}
|
||||
}
|
||||
|
||||
$data->setComment($forms['comment']->getData());
|
||||
$data->setDontCheckQuantity($forms['dontCheckQuantity']->getData());
|
||||
|
||||
if (isset($forms['buildsPartLot'])) {
|
||||
$lot = $forms['buildsPartLot']->getData();
|
||||
if (!$lot) { //When the user selected "Create new lot", create a new lot
|
||||
$lot = new PartLot();
|
||||
$description = 'Build ' . date('Y-m-d H:i:s');
|
||||
if ($data->getComment() !== '') {
|
||||
$description .= ' (' . $data->getComment() . ')';
|
||||
}
|
||||
$lot->setDescription($description);
|
||||
|
||||
$data->getAssembly()->getBuildPart()->addPartLot($lot);
|
||||
}
|
||||
|
||||
$data->setBuildsPartLot($lot);
|
||||
}
|
||||
//This has to be set after the builds part lot, so that it can disable the option
|
||||
$data->setAddBuildsToBuildsPart($forms['addBuildsToBuildsPart']->getData());
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\Form\Filters;
|
||||
|
||||
use App\DataTables\Filters\AttachmentFilter;
|
||||
use App\Entity\Attachments\AssemblyAttachment;
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Attachments\AttachmentTypeAttachment;
|
||||
use App\Entity\Attachments\CategoryAttachment;
|
||||
|
|
@ -80,6 +81,7 @@ class AttachmentFilterType extends AbstractType
|
|||
'category.label' => CategoryAttachment::class,
|
||||
'currency.label' => CurrencyAttachment::class,
|
||||
'project.label' => ProjectAttachment::class,
|
||||
'assembly.label' => AssemblyAttachment::class,
|
||||
'footprint.label' => FootprintAttachment::class,
|
||||
'group.label' => GroupAttachment::class,
|
||||
'label_profile.label' => LabelAttachment::class,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class ProjectAddPartsType extends AbstractType
|
|||
],
|
||||
'constraints' => [
|
||||
new UniqueObjectCollection(message: 'project.bom_entry.part_already_in_bom', fields: ['part']),
|
||||
new UniqueObjectCollection(message: 'project.bom_entry.assembly_already_in_bom', fields: ['assembly']),
|
||||
new UniqueObjectCollection(message: 'project.bom_entry.name_already_in_bom', fields: ['name']),
|
||||
]
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace App\Form\ProjectSystem;
|
||||
|
||||
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||
use App\Form\Type\AssemblySelectType;
|
||||
use App\Form\Type\BigDecimalNumberType;
|
||||
use App\Form\Type\CurrencyEntityType;
|
||||
use App\Form\Type\PartSelectType;
|
||||
|
|
@ -22,8 +23,6 @@ class ProjectBOMEntryType extends AbstractType
|
|||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
|
||||
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) {
|
||||
$form = $event->getForm();
|
||||
/** @var ProjectBOMEntry $data */
|
||||
|
|
@ -36,11 +35,14 @@ class ProjectBOMEntryType extends AbstractType
|
|||
});
|
||||
|
||||
$builder
|
||||
|
||||
->add('part', PartSelectType::class, [
|
||||
'label' => 'project.bom.part',
|
||||
'required' => false,
|
||||
])
|
||||
->add('assembly', AssemblySelectType::class, [
|
||||
'label' => 'project.bom.assembly',
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'project.bom.name',
|
||||
'required' => false,
|
||||
|
|
@ -77,10 +79,7 @@ class ProjectBOMEntryType extends AbstractType
|
|||
'required' => false,
|
||||
'label' => false,
|
||||
'short' => true,
|
||||
])
|
||||
|
||||
;
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace App\Form\ProjectSystem;
|
||||
|
||||
use App\Helpers\Assemblies\AssemblyBuildRequest;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
|
|
@ -38,10 +39,11 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ProjectBuildType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
public function __construct(private readonly Security $security)
|
||||
public function __construct(private readonly Security $security, private readonly TranslatorInterface $translator)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -82,36 +84,54 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface
|
|||
//The form is initially empty, we have to define the fields after we know the data
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) {
|
||||
$form = $event->getForm();
|
||||
/** @var ProjectBuildRequest $build_request */
|
||||
$build_request = $event->getData();
|
||||
/** @var ProjectBuildRequest $projectBuildRequest */
|
||||
$projectBuildRequest = $event->getData();
|
||||
|
||||
$form->add('addBuildsToBuildsPart', CheckboxType::class, [
|
||||
'label' => 'project.build.add_builds_to_builds_part',
|
||||
'required' => false,
|
||||
'disabled' => !$build_request->getProject()->getBuildPart() instanceof Part,
|
||||
'disabled' => !$projectBuildRequest->getProject()->getBuildPart() instanceof Part,
|
||||
]);
|
||||
|
||||
if ($build_request->getProject()->getBuildPart() instanceof Part) {
|
||||
if ($projectBuildRequest->getProject()->getBuildPart() instanceof Part) {
|
||||
$form->add('buildsPartLot', PartLotSelectType::class, [
|
||||
'label' => 'project.build.builds_part_lot',
|
||||
'required' => false,
|
||||
'part' => $build_request->getProject()->getBuildPart(),
|
||||
'part' => $projectBuildRequest->getProject()->getBuildPart(),
|
||||
'placeholder' => 'project.build.buildsPartLot.new_lot'
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($build_request->getPartBomEntries() as $bomEntry) {
|
||||
foreach ($projectBuildRequest->getPartBomEntries() as $bomEntry) {
|
||||
//Every part lot has a field to specify the number of parts to take from this lot
|
||||
foreach ($build_request->getPartLotsForBOMEntry($bomEntry) as $lot) {
|
||||
foreach ($projectBuildRequest->getPartLotsForBOMEntry($bomEntry) as $lot) {
|
||||
$form->add('lot_' . $lot->getID(), SIUnitType::class, [
|
||||
'label' => false,
|
||||
'measurement_unit' => $bomEntry->getPart()->getPartUnit(),
|
||||
'max' => min($build_request->getNeededAmountForBOMEntry($bomEntry), $lot->getAmount()),
|
||||
'max' => min($projectBuildRequest->getNeededAmountForBOMEntry($bomEntry), $lot->getAmount()),
|
||||
'disabled' => !$this->security->isGranted('withdraw', $lot),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($projectBuildRequest->getAssemblyBomEntries() as $bomEntry) {
|
||||
$assemblyBuildRequest = new AssemblyBuildRequest($bomEntry->getAssembly(), $projectBuildRequest->getNumberOfBuilds());
|
||||
|
||||
//Add fields for assembly bom entries
|
||||
foreach ($assemblyBuildRequest->getPartBomEntries() as $partBomEntry) {
|
||||
foreach ($assemblyBuildRequest->getPartLotsForBOMEntry($partBomEntry) as $lot) {
|
||||
$form->add('lot_' . $lot->getID(), SIUnitType::class, [
|
||||
'label' => $this->translator->trans('project.build.builds_part_lot_label', [
|
||||
'%name%' => $partBomEntry->getPart()->getName(),
|
||||
'%quantity%' => $partBomEntry->getQuantity() * $projectBuildRequest->getNumberOfBuilds()
|
||||
]),
|
||||
'measurement_unit' => $partBomEntry->getPart()->getPartUnit(),
|
||||
'max' => min($assemblyBuildRequest->getNeededAmountForBOMEntry($partBomEntry), $lot->getAmount()),
|
||||
'disabled' => !$this->security->isGranted('withdraw', $lot),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
125
src/Form/Type/AssemblySelectType.php
Normal file
125
src/Form/Type/AssemblySelectType.php
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Services\Attachments\AssemblyPreviewGenerator;
|
||||
use App\Services\Attachments\AttachmentURLGenerator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceList;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\Event\PreSetDataEvent;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class AssemblySelectType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em, private readonly AssemblyPreviewGenerator $previewGenerator, private readonly AttachmentURLGenerator $attachmentURLGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
//At initialization, we have to fill the form element with our selected data, so the user can see it
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) {
|
||||
$form = $event->getForm();
|
||||
$config = $form->getConfig()->getOptions();
|
||||
$data = $event->getData() ?? [];
|
||||
|
||||
$config['compound'] = false;
|
||||
$config['choices'] = is_iterable($data) ? $data : [$data];
|
||||
$config['error_bubbling'] = true;
|
||||
|
||||
$form->add('autocomplete', EntityType::class, $config);
|
||||
});
|
||||
|
||||
//After form submit, we have to add the selected element as choice, otherwise the form will not accept this element
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
|
||||
$data = $event->getData();
|
||||
$form = $event->getForm();
|
||||
$options = $form->get('autocomplete')->getConfig()->getOptions();
|
||||
|
||||
|
||||
if (!isset($data['autocomplete']) || '' === $data['autocomplete'] || empty($data['autocomplete'])) {
|
||||
$options['choices'] = [];
|
||||
} else {
|
||||
//Extract the ID from the submitted data
|
||||
$id = $data['autocomplete'];
|
||||
//Find the element in the database
|
||||
$element = $this->em->find($options['class'], $id);
|
||||
|
||||
//Add the element as choice
|
||||
$options['choices'] = [$element];
|
||||
$options['error_bubbling'] = true;
|
||||
$form->add('autocomplete', EntityType::class, $options);
|
||||
}
|
||||
});
|
||||
|
||||
$builder->setDataMapper($this);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => Assembly::class,
|
||||
'choice_label' => 'name',
|
||||
'placeholder' => 'None',
|
||||
'compound' => true,
|
||||
'error_bubbling' => false,
|
||||
]);
|
||||
|
||||
error_log($this->urlGenerator->generate('typeahead_assemblies', ['query' => '__QUERY__']));
|
||||
|
||||
$resolver->setDefaults([
|
||||
'attr' => [
|
||||
'data-controller' => 'elements--assembly-select',
|
||||
'data-autocomplete' => $this->urlGenerator->generate('typeahead_assemblies', ['query' => '__QUERY__']),
|
||||
'autocomplete' => 'off',
|
||||
],
|
||||
]);
|
||||
|
||||
$resolver->setDefaults([
|
||||
//Prefill the selected choice with the needed data, so the user can see it without an additional Ajax request
|
||||
'choice_attr' => ChoiceList::attr($this, function (?Assembly $assembly) {
|
||||
if($assembly instanceof Assembly) {
|
||||
//Determine the picture to show:
|
||||
$preview_attachment = $this->previewGenerator->getTablePreviewAttachment($assembly);
|
||||
if ($preview_attachment instanceof Attachment) {
|
||||
$preview_url = $this->attachmentURLGenerator->getThumbnailURL($preview_attachment,
|
||||
'thumbnail_sm');
|
||||
} else {
|
||||
$preview_url = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $assembly instanceof Assembly ? [
|
||||
'data-description' => $assembly->getDescription() ? mb_strimwidth($assembly->getDescription(), 0, 127, '...') : '',
|
||||
'data-category' => '',
|
||||
'data-footprint' => '',
|
||||
'data-image' => $preview_url,
|
||||
] : [];
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
public function mapDataToForms($data, \Traversable $forms): void
|
||||
{
|
||||
$form = current(iterator_to_array($forms, false));
|
||||
$form->setData($data);
|
||||
}
|
||||
|
||||
public function mapFormsToData(\Traversable $forms, &$data): void
|
||||
{
|
||||
$form = current(iterator_to_array($forms, false));
|
||||
$data = $form->getData();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ class PartSelectType extends AbstractType implements DataMapperInterface
|
|||
$options = $form->get('autocomplete')->getConfig()->getOptions();
|
||||
|
||||
|
||||
if (!isset($data['autocomplete']) || '' === $data['autocomplete']) {
|
||||
if (!isset($data['autocomplete']) || '' === $data['autocomplete'] || empty($data['autocomplete'])) {
|
||||
$options['choices'] = [];
|
||||
} else {
|
||||
//Extract the ID from the submitted data
|
||||
|
|
@ -84,7 +84,6 @@ class PartSelectType extends AbstractType implements DataMapperInterface
|
|||
'data-autocomplete' => $this->urlGenerator->generate('typeahead_parts', ['query' => '__QUERY__']),
|
||||
//Disable browser autocomplete
|
||||
'autocomplete' => 'off',
|
||||
|
||||
],
|
||||
]);
|
||||
|
||||
|
|
@ -103,7 +102,7 @@ class PartSelectType extends AbstractType implements DataMapperInterface
|
|||
}
|
||||
|
||||
return $part instanceof Part ? [
|
||||
'data-description' => mb_strimwidth($part->getDescription(), 0, 127, '...'),
|
||||
'data-description' => $part->getDescription() ? mb_strimwidth($part->getDescription(), 0, 127, '...') : '',
|
||||
'data-category' => $part->getCategory() instanceof Category ? $part->getCategory()->getName() : '',
|
||||
'data-footprint' => $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getName() : '',
|
||||
'data-image' => $preview_url,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue