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 %}
+
+
+ {% if not can_build %}
+
{% trans %}project.builds.build_not_possible{% endtrans %}
+ {% trans %}project.builds.following_bom_entries_miss_instock{% endtrans %}
+
+ {% for bom_entry in buildHelper.nonBuildableProjectBomEntries(project) %}
+