diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index b1c31edb..24c61508 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -92,7 +92,9 @@ class ProjectController extends AbstractController $form->handleRequest($request); if ($form->isSubmitted()) { if ($form->isValid()) { - $buildHelper->doWithdrawForProjectBuildRequest($projectBuildRequest); + //We have to do a flush already here, so that the newly created partLot gets an ID and can be logged to DB later. + $entityManager->flush(); + $buildHelper->doBuild($projectBuildRequest); $entityManager->flush(); $this->addFlash('success', 'project.build.flash.success'); diff --git a/src/Form/ProjectSystem/ProjectBuildType.php b/src/Form/ProjectSystem/ProjectBuildType.php index 0bcf98ab..f092ad82 100644 --- a/src/Form/ProjectSystem/ProjectBuildType.php +++ b/src/Form/ProjectSystem/ProjectBuildType.php @@ -20,11 +20,14 @@ namespace App\Form\ProjectSystem; +use App\Entity\Parts\PartLot; +use App\Form\Type\PartLotSelectType; use App\Form\Type\SIUnitType; use App\Helpers\Projects\ProjectBuildRequest; 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; @@ -53,15 +56,32 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface $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 ProjectBuildRequest $build_request */ $build_request = $event->getData(); + $form->add('addBuildsToBuildsPart', CheckboxType::class, [ + 'label' => 'project.build.add_builds_to_builds_part', + 'required' => false, + 'disabled' => $build_request->getProject()->getBuildPart() === null, + ]); + + if ($build_request->getProject()->getBuildPart()) { + $form->add('buildsPartLot', PartLotSelectType::class, [ + 'label' => 'project.build.builds_part_lot', + 'required' => false, + 'part' => $build_request->getProject()->getBuildPart(), + 'placeholder' => 'project.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) { @@ -94,6 +114,10 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface } $forms['comment']->setData($data->getComment()); + $forms['addBuildsToBuildsPart']->setData($data->getAddBuildsToBuildsPart()); + if (isset($forms['buildsPartLot'])) { + $forms['buildsPartLot']->setData($data->getBuildsPartLot()); + } } @@ -116,5 +140,22 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface } $data->setComment($forms['comment']->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 (!empty($data->getComment())) { + $description .= ' (' . $data->getComment() . ')'; + } + $lot->setDescription($description); + + $data->getProject()->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()); } } \ No newline at end of file diff --git a/src/Form/Type/PartLotSelectType.php b/src/Form/Type/PartLotSelectType.php new file mode 100644 index 00000000..aa3c6fc6 --- /dev/null +++ b/src/Form/Type/PartLotSelectType.php @@ -0,0 +1,63 @@ +. + */ + +namespace App\Form\Type; + +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use Doctrine\ORM\EntityRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceList; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class PartLotSelectType extends AbstractType +{ + public function getParent() + { + return EntityType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired('part'); + $resolver->setAllowedTypes('part', Part::class); + + $resolver->setDefaults([ + 'class' => PartLot::class, + 'choice_label' => ChoiceList::label($this, function (PartLot $part_lot) { + return ($part_lot->getStorageLocation() ? $part_lot->getStorageLocation()->getFullPath() : '') + . ' (' . $part_lot->getName() . '): ' . $part_lot->getAmount(); + }), + 'attr' => [ + 'data-controller' => 'elements--selectpicker', + 'data-live-search' => true, + ], + 'query_builder' => function (Options $options) { + return function (EntityRepository $er) use ($options) { + return $er->createQueryBuilder('l') + ->where('l.part = :part') + ->setParameter('part', $options['part']); + }; + } + ]); + } +} \ No newline at end of file diff --git a/src/Helpers/Projects/ProjectBuildRequest.php b/src/Helpers/Projects/ProjectBuildRequest.php index 8cf94063..0e3b864f 100644 --- a/src/Helpers/Projects/ProjectBuildRequest.php +++ b/src/Helpers/Projects/ProjectBuildRequest.php @@ -40,6 +40,10 @@ final class ProjectBuildRequest private string $comment = ''; + private ?PartLot $builds_lot = null; + + private bool $add_build_to_builds_part = false; + /** * @param Project $project The project that should be build * @param int $number_of_builds The number of builds that should be created @@ -50,6 +54,17 @@ final class ProjectBuildRequest $this->number_of_builds = $number_of_builds; $this->initializeArray(); + + //By default, use the first available lot of builds part if there is one. + if($project->getBuildPart() !== null) { + $this->add_build_to_builds_part = true; + foreach( $project->getBuildPart()->getPartLots() as $lot) { + if (!$lot->isInstockUnknown()) { + $this->builds_lot = $lot; + break; + } + } + } } private function initializeArray(): void @@ -80,6 +95,62 @@ final class ProjectBuildRequest } } + /** + * Returns the partlot where the builds should be added to, or null if it should not be added to any lot. + * @return PartLot|null + */ + public function getBuildsPartLot(): ?PartLot + { + return $this->builds_lot; + } + + /** + * Return if the builds should be added to the builds part of this project as new stock + * @return bool + */ + public function getAddBuildsToBuildsPart(): bool + { + return $this->add_build_to_builds_part; + } + + /** + * Set if the builds should be added to the builds part of this project as new stock + * @param bool $new_value + * @return $this + */ + public function setAddBuildsToBuildsPart(bool $new_value): self + { + $this->add_build_to_builds_part = $new_value; + + if ($new_value === false) { + $this->builds_lot = null; + } + + return $this; + } + + /** + * Set the partlot where the builds should be added to, or null if it should not be added to any lot. + * The part lot must belong to the project build part, or an exception is thrown! + * @param PartLot|null $new_part_lot + * @return $this + */ + public function setBuildsPartLot(?PartLot $new_part_lot): self + { + //Ensure that this new_part_lot belongs to the project + if (($new_part_lot !== null && $new_part_lot->getPart() !== $this->project->getBuildPart()) || $this->project->getBuildPart() === null) { + throw new \InvalidArgumentException('The given part lot does not belong to the projects build part!'); + } + + if ($new_part_lot !== null) { + $this->setAddBuildsToBuildsPart(true); + } + + $this->builds_lot = $new_part_lot; + + return $this; + } + /** * Returns the comment where the user can write additional information about the build. * @return string diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index d18e1767..8eee0772 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -135,13 +135,13 @@ class ProjectBuildHelper } /** - * Withdraw the parts from the stock using the given ProjectBuildRequest. + * Withdraw the parts from the stock using the given ProjectBuildRequest and create the build parts entries, if needed. * The ProjectBuildRequest has to be validated before!! * You have to flush changes to DB afterwards * @param ProjectBuildRequest $buildRequest * @return void */ - public function doWithdrawForProjectBuildRequest(ProjectBuildRequest $buildRequest): void + public function doBuild(ProjectBuildRequest $buildRequest): void { $message = $buildRequest->getComment(); $message .= ' (Project build: '.$buildRequest->getProject()->getName().')'; @@ -154,5 +154,9 @@ class ProjectBuildHelper } } } + + if ($buildRequest->getAddBuildsToBuildsPart()) { + $this->withdraw_add_helper->add($buildRequest->getBuildsPartLot(), $buildRequest->getNumberOfBuilds(), $message); + } } } \ No newline at end of file diff --git a/templates/Projects/build/_form.html.twig b/templates/Projects/build/_form.html.twig index b6c13fa1..4a02dd4d 100644 --- a/templates/Projects/build/_form.html.twig +++ b/templates/Projects/build/_form.html.twig @@ -75,6 +75,11 @@ {{ form_row(form.comment) }} +{{ form_row(form.addBuildsToBuildsPart) }} +{% if form.buildsPartLot is defined %} + {{ form_row(form.buildsPartLot) }} +{% endif %} + {{ form_row(form.submit) }} {{ form_end(form) }} \ No newline at end of file diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 180fd282..f90a153b 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -10369,5 +10369,23 @@ Element 3 Choose from which part lots the stock to build this project should be taken (and in which amount). Check the checkbox for each BOM Entry, when you are finished withdrawing the parts, or use the top checkbox to check all boxes at once. + + + project.build.buildsPartLot.new_lot + Create new lot + + + + + project.build.add_builds_to_builds_part + Add builds to project builds part + + + + + project.build.builds_part_lot + Target lot + +