From 76ec63e7602387e7cf580a4294c481857f0dd554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 18 Jan 2023 23:07:51 +0100 Subject: [PATCH] Added a tab "Build" to project info page, where you can see how often you can build this project. --- src/Controller/ProjectController.php | 4 +- src/Entity/ProjectSystem/ProjectBOMEntry.php | 9 +- .../ProjectSystem/ProjectBuildHelper.php | 115 +++++++++++++++++ .../ProjectSystem/ProjectBuildPartHelper.php | 3 + templates/Projects/info/_builds.html.twig | 28 +++++ templates/Projects/info/info.html.twig | 10 ++ templates/components/projects.macro.html.twig | 8 ++ .../ProjectSystem/ProjectBuildHelperTest.php | 116 ++++++++++++++++++ .../ProjectBuildPartHelperTest.php | 53 ++++++++ translations/messages.en.xlf | 42 +++++++ 10 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 src/Services/ProjectSystem/ProjectBuildHelper.php create mode 100644 templates/Projects/info/_builds.html.twig create mode 100644 templates/components/projects.macro.html.twig create mode 100644 tests/Services/ProjectSystem/ProjectBuildHelperTest.php create mode 100644 tests/Services/ProjectSystem/ProjectBuildPartHelperTest.php diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index f4e385cb..d831dc3d 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -26,6 +26,7 @@ use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Form\ProjectSystem\ProjectBOMEntryCollectionType; use App\Form\Type\StructuralEntityType; +use App\Services\ProjectSystem\ProjectBuildHelper; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use Omines\DataTablesBundle\DataTableFactory; @@ -52,7 +53,7 @@ class ProjectController extends AbstractController /** * @Route("/{id}/info", name="project_info", requirements={"id"="\d+"}) */ - public function info(Project $project, Request $request) + public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper) { $this->denyAccessUnlessGranted('read', $project); @@ -64,6 +65,7 @@ class ProjectController extends AbstractController } return $this->render('Projects/info/info.html.twig', [ + 'buildHelper' => $buildHelper, 'datatable' => $table, 'project' => $project, ]); diff --git a/src/Entity/ProjectSystem/ProjectBOMEntry.php b/src/Entity/ProjectSystem/ProjectBOMEntry.php index 3c874a7b..9d8be815 100644 --- a/src/Entity/ProjectSystem/ProjectBOMEntry.php +++ b/src/Entity/ProjectSystem/ProjectBOMEntry.php @@ -259,7 +259,14 @@ class ProjectBOMEntry extends AbstractDBElement $this->price_currency = $price_currency; } - + /** + * Checks whether this BOM entry is a part associated BOM entry or not. + * @return bool True if this BOM entry is a part associated BOM entry, false otherwise. + */ + public function isPartBomEntry(): bool + { + return $this->part !== null; + } /** * @Assert\Callback diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php new file mode 100644 index 00000000..40545834 --- /dev/null +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -0,0 +1,115 @@ +. + */ + +namespace App\Services\ProjectSystem; + +use App\Entity\ProjectSystem\Project; +use App\Entity\ProjectSystem\ProjectBOMEntry; + +class ProjectBuildHelper +{ + + /** + * Returns the maximum buildable amount of the given BOM entry based on the stock of the used parts. + * This function only works for BOM entries that are associated with a part. + * @param ProjectBOMEntry $projectBOMEntry + * @return int + */ + public function getMaximumBuildableCountForBOMEntry(ProjectBOMEntry $projectBOMEntry): int + { + $part = $projectBOMEntry->getPart(); + + if ($part === null) { + throw new \InvalidArgumentException('This function cannot determine the maximum buildable count for a BOM entry without a part!'); + } + + if ($projectBOMEntry->getQuantity() <= 0) { + throw new \RuntimeException('The quantity of the BOM entry must be greater than 0!'); + } + + $amount_sum = $part->getAmountSum(); + + return (int) floor($amount_sum / $projectBOMEntry->getQuantity()); + } + + /** + * Returns the maximum buildable amount of the given project, based on the stock of the used parts in the BOM. + * @param Project $project + * @return int + */ + public function getMaximumBuildableCount(Project $project): int + { + $maximum_buildable_count = PHP_INT_MAX; + foreach ($project->getBOMEntries() as $bom_entry) { + //Skip BOM entries without a part (as we can not determine that) + if (!$bom_entry->isPartBomEntry()) { + continue; + } + + //The maximum buildable count for the whole project is the minimum of all BOM entries + $maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry)); + } + + return $maximum_buildable_count; + } + + /** + * Checks if the given project can be build with the current stock. + * This means that the maximum buildable count is greater than 0. + * @param Project $project + * @return bool + */ + public function isProjectBuildable(Project $project): bool + { + return $this->getMaximumBuildableCount($project) > 0; + } + + /** + * Returns the project BOM entries for which parts are missing in the stock for the given number of builds + * @param Project $project The project for which the BOM entries should be checked + * @param int $number_of_builds How often should the project be build? + * @return ProjectBOMEntry[] + */ + public function getNonBuildableProjectBomEntries(Project $project, int $number_of_builds = 1): array + { + if ($number_of_builds < 1) { + throw new \InvalidArgumentException('The number of builds must be greater than 0!'); + } + + $non_buildable_entries = []; + + foreach ($project->getBomEntries() as $bomEntry) { + $part = $bomEntry->getPart(); + + //Skip BOM entries without a part (as we can not determine that) + if ($part === null) { + continue; + } + + $amount_sum = $part->getAmountSum(); + + if ($amount_sum < $bomEntry->getQuantity() * $number_of_builds) { + $non_buildable_entries[] = $bomEntry; + } + } + + return $non_buildable_entries; + } +} \ No newline at end of file diff --git a/src/Services/ProjectSystem/ProjectBuildPartHelper.php b/src/Services/ProjectSystem/ProjectBuildPartHelper.php index 5ec1537b..136e2ff7 100644 --- a/src/Services/ProjectSystem/ProjectBuildPartHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildPartHelper.php @@ -29,6 +29,9 @@ class ProjectBuildPartHelper //Add a tag to the part that indicates that it is a build part $part->setTags('project-build'); + //Associate the part with the project + $project->setBuildPart($part); + return $part; } } \ No newline at end of file diff --git a/templates/Projects/info/_builds.html.twig b/templates/Projects/info/_builds.html.twig new file mode 100644 index 00000000..170edbb9 --- /dev/null +++ b/templates/Projects/info/_builds.html.twig @@ -0,0 +1,28 @@ +{% set can_build = buildHelper.projectBuildable(project) %} + +{% import "components/projects.macro.html.twig" as project_macros %} + + + + +
+
+
+ + +
+
+
\ No newline at end of file diff --git a/templates/Projects/info/info.html.twig b/templates/Projects/info/info.html.twig index ce920e9e..9a57e55c 100644 --- a/templates/Projects/info/info.html.twig +++ b/templates/Projects/info/info.html.twig @@ -47,6 +47,13 @@ {{ project.bomEntries | length }} + {% if project.attachments is not empty %}