From 1611b6cd4190296352b3bcbd303ca4599e732cc9 Mon Sep 17 00:00:00 2001 From: MayNiklas Date: Wed, 15 Apr 2026 11:01:12 +0200 Subject: [PATCH] Add unit price and extended price columns to project BOM table Adds two optional columns to the project BOM datatable (hidden by default, toggleable via column visibility): - **Price**: unit price for the BOM entry in the base currency, looked up via PricedetailHelper. For parts whose BOM quantity falls below the minimum order amount the minimum order amount is used for the price tier lookup so that a price is always returned. - **Extended Price**: unit price multiplied by the BOM quantity. Prices are rendered via MoneyFormatter (locale-aware, with currency symbol). Both columns round up to 2 decimal places to avoid displaying 0.00 for very small prices. --- src/DataTables/ProjectBomEntriesDataTable.php | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index 04d8206b..6ee726e8 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -29,12 +29,16 @@ use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\MarkdownColumn; use App\DataTables\Helpers\PartDataTableHelper; use App\Doctrine\Helpers\FieldHelper; -use App\Entity\Parts\Part; use App\Entity\Parts\ManufacturingStatus; +use App\Entity\Parts\Part; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Services\ElementTypeNameGenerator; use App\Services\EntityURLGenerator; use App\Services\Formatters\AmountFormatter; +use App\Services\Formatters\MoneyFormatter; +use App\Services\Parts\PricedetailHelper; +use Brick\Math\BigDecimal; +use Brick\Math\RoundingMode; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; @@ -50,7 +54,9 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface protected EntityURLGenerator $entityURLGenerator, protected TranslatorInterface $translator, protected AmountFormatter $amountFormatter, - protected PartDataTableHelper $partDataTableHelper + protected PartDataTableHelper $partDataTableHelper, + protected PricedetailHelper $pricedetailHelper, + protected MoneyFormatter $moneyFormatter, ) { } @@ -202,6 +208,27 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface return ''; } ]) + ->add('price', TextColumn::class, [ + 'label' => 'project.bom.price', + 'visible' => false, + 'render' => function ($value, ProjectBOMEntry $context) { + $price = $this->getBomEntryUnitPrice($context); + return $this->moneyFormatter->format($price->toScale(2, RoundingMode::UP)->toFloat(), null, 2, true); + }, + ]) + ->add('ext_price', TextColumn::class, [ + 'label' => 'project.bom.ext_price', + 'visible' => false, + 'render' => function ($value, ProjectBOMEntry $context) { + $price = $this->getBomEntryUnitPrice($context); + return $this->moneyFormatter->format( + $price->multipliedBy($context->getQuantity())->toScale(2, RoundingMode::UP)->toFloat(), + null, + 2, + true + ); + }, + ]) ->add('addedDate', LocaleDateTimeColumn::class, [ 'label' => $this->translator->trans('part.table.addedDate'), @@ -231,6 +258,21 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ]); } + private function getBomEntryUnitPrice(ProjectBOMEntry $entry): BigDecimal + { + if ($entry->getPart() instanceof Part) { + $amount = $entry->getQuantity(); + // If the BOM quantity is below the minimum order amount, use the minimum order amount + // for the price lookup — otherwise calculateAvgPrice returns null (no price tier matches). + $minOrderAmount = $this->pricedetailHelper->getMinOrderAmount($entry->getPart()); + if ($minOrderAmount !== null) { + $amount = max($amount, $minOrderAmount); + } + return $this->pricedetailHelper->calculateAvgPrice($entry->getPart(), $amount) ?? BigDecimal::zero(); + } + return $entry->getPrice() ?? BigDecimal::zero(); + } + private function getFilterQuery(QueryBuilder $builder, array $options): void { $builder