mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-05-18 17:31:35 +00:00
Deduplicate BOM entry price logic into ProjectBuildHelper
The private getBomEntryUnitPrice() in ProjectBomEntriesDataTable was identical to the one in ProjectBuildHelper. Replaced it with a new public getEntryUnitPrice() on ProjectBuildHelper (returns BigDecimal, never null) and delegate to it from the DataTable. This eliminates the duplicate code and brings the DataTable lines under the existing ProjectBuildHelper test coverage. Added three tests for getEntryUnitPrice() covering the no-pricing, non-part, and part cases.
This commit is contained in:
parent
5d669da932
commit
fa1e5549f0
3 changed files with 45 additions and 20 deletions
|
|
@ -36,8 +36,7 @@ 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 App\Services\ProjectSystem\ProjectBuildHelper;
|
||||
use Brick\Math\RoundingMode;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\ORM\Query;
|
||||
|
|
@ -55,7 +54,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
protected TranslatorInterface $translator,
|
||||
protected AmountFormatter $amountFormatter,
|
||||
protected PartDataTableHelper $partDataTableHelper,
|
||||
protected PricedetailHelper $pricedetailHelper,
|
||||
protected ProjectBuildHelper $projectBuildHelper,
|
||||
protected MoneyFormatter $moneyFormatter,
|
||||
) {
|
||||
}
|
||||
|
|
@ -212,7 +211,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
'label' => 'project.bom.price',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
$price = $this->getBomEntryUnitPrice($context);
|
||||
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
|
||||
return $this->moneyFormatter->format($price->toScale(2, RoundingMode::UP)->toFloat(), null, 2, true);
|
||||
},
|
||||
])
|
||||
|
|
@ -220,7 +219,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
|||
'label' => 'project.bom.ext_price',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
$price = $this->getBomEntryUnitPrice($context);
|
||||
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
|
||||
return $this->moneyFormatter->format(
|
||||
$price->multipliedBy($context->getQuantity())->toScale(2, RoundingMode::UP)->toFloat(),
|
||||
null,
|
||||
|
|
@ -258,21 +257,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -227,6 +227,16 @@ final readonly class ProjectBuildHelper
|
|||
?->toScale(2, RoundingMode::UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective unit price for a single piece of the given BOM entry,
|
||||
* taking bulk pricing and minimum order amounts into account for N builds.
|
||||
* Returns BigDecimal::zero() when no pricing data is available.
|
||||
*/
|
||||
public function getEntryUnitPrice(ProjectBOMEntry $entry, int $number_of_builds = 1, ?Currency $currency = null): BigDecimal
|
||||
{
|
||||
return $this->getBomEntryUnitPrice($entry, $number_of_builds, $currency) ?? BigDecimal::zero();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective unit price for a single piece of the given BOM entry,
|
||||
* taking bulk pricing into account for N builds.
|
||||
|
|
|
|||
|
|
@ -264,6 +264,37 @@ final class ProjectBuildHelperTest extends WebTestCase
|
|||
$this->assertTrue(BigDecimal::of('11.00')->isEqualTo($result));
|
||||
}
|
||||
|
||||
public function testGetEntryUnitPriceReturnsZeroForNoPricingData(): void
|
||||
{
|
||||
$entry = new ProjectBOMEntry();
|
||||
$entry->setPart(new Part()); // part with no orderdetails
|
||||
$entry->setQuantity(5);
|
||||
|
||||
$result = $this->service->getEntryUnitPrice($entry);
|
||||
$this->assertTrue(BigDecimal::zero()->isEqualTo($result));
|
||||
}
|
||||
|
||||
public function testGetEntryUnitPriceNonPartEntry(): void
|
||||
{
|
||||
$entry = new ProjectBOMEntry();
|
||||
$entry->setName('Wire');
|
||||
$entry->setQuantity(2);
|
||||
$entry->setPrice(BigDecimal::of('1.25'));
|
||||
|
||||
$result = $this->service->getEntryUnitPrice($entry);
|
||||
$this->assertTrue(BigDecimal::of('1.25')->isEqualTo($result));
|
||||
}
|
||||
|
||||
public function testGetEntryUnitPriceWithPart(): void
|
||||
{
|
||||
$entry = new ProjectBOMEntry();
|
||||
$entry->setPart($this->makePartWithPrice(2.00));
|
||||
$entry->setQuantity(3);
|
||||
|
||||
$result = $this->service->getEntryUnitPrice($entry);
|
||||
$this->assertTrue(BigDecimal::of('2.00')->isEqualTo($result));
|
||||
}
|
||||
|
||||
public function testCalculateTotalBuildPriceRespectsMinOrderAmount(): void
|
||||
{
|
||||
$project = new Project();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue