|
25 | 25 | use App\Entity\Parts\Part; |
26 | 26 | use App\Entity\ProjectSystem\Project; |
27 | 27 | use App\Entity\ProjectSystem\ProjectBOMEntry; |
| 28 | +use App\Entity\PriceInformations\Currency; |
28 | 29 | use App\Helpers\Projects\ProjectBuildRequest; |
29 | 30 | use App\Services\Parts\PartLotWithdrawAddHelper; |
| 31 | +use App\Services\Parts\PricedetailHelper; |
| 32 | +use Brick\Math\BigDecimal; |
| 33 | +use Brick\Math\RoundingMode; |
30 | 34 |
|
31 | 35 | /** |
32 | 36 | * @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest |
33 | 37 | */ |
34 | 38 | final readonly class ProjectBuildHelper |
35 | 39 | { |
36 | | - public function __construct(private PartLotWithdrawAddHelper $withdraw_add_helper) |
37 | | - { |
| 40 | + public function __construct( |
| 41 | + private PartLotWithdrawAddHelper $withdraw_add_helper, |
| 42 | + private PricedetailHelper $pricedetailHelper, |
| 43 | + ) { |
38 | 44 | } |
39 | 45 |
|
40 | 46 | /** |
@@ -168,4 +174,81 @@ public function doBuild(ProjectBuildRequest $buildRequest): void |
168 | 174 | $this->withdraw_add_helper->add($buildRequest->getBuildsPartLot(), $buildRequest->getNumberOfBuilds(), $message); |
169 | 175 | } |
170 | 176 | } |
| 177 | + |
| 178 | + /** |
| 179 | + * Calculates the total price to build the given project N times, taking bulk pricing into account. |
| 180 | + * Returns null if no BOM entry has any pricing information. |
| 181 | + */ |
| 182 | + public function calculateTotalBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal |
| 183 | + { |
| 184 | + $total = BigDecimal::zero(); |
| 185 | + $has_price = false; |
| 186 | + |
| 187 | + foreach ($project->getBomEntries() as $entry) { |
| 188 | + $unit_price = $this->getBomEntryUnitPrice($entry, $number_of_builds, $currency); |
| 189 | + if ($unit_price === null) { |
| 190 | + continue; |
| 191 | + } |
| 192 | + $has_price = true; |
| 193 | + $total = $total->plus($unit_price->multipliedBy($entry->getQuantity())->multipliedBy($number_of_builds)); |
| 194 | + } |
| 195 | + |
| 196 | + return $has_price ? $total : null; |
| 197 | + } |
| 198 | + |
| 199 | + /** |
| 200 | + * Calculates the price to build one unit of the given project when ordering for N builds in total. |
| 201 | + * Returns null if no BOM entry has any pricing information. |
| 202 | + */ |
| 203 | + public function calculateUnitBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal |
| 204 | + { |
| 205 | + $total = $this->calculateTotalBuildPrice($project, $number_of_builds, $currency); |
| 206 | + if ($total === null) { |
| 207 | + return null; |
| 208 | + } |
| 209 | + return $total->dividedBy($number_of_builds, 10, RoundingMode::HALF_UP); |
| 210 | + } |
| 211 | + |
| 212 | + /** |
| 213 | + * Returns the total build price rounded up to 2 decimal places, ready for display. |
| 214 | + */ |
| 215 | + public function roundedTotalBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal |
| 216 | + { |
| 217 | + return $this->calculateTotalBuildPrice($project, $number_of_builds, $currency) |
| 218 | + ?->toScale(2, RoundingMode::UP); |
| 219 | + } |
| 220 | + |
| 221 | + /** |
| 222 | + * Returns the unit build price rounded up to 2 decimal places, ready for display. |
| 223 | + */ |
| 224 | + public function roundedUnitBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal |
| 225 | + { |
| 226 | + return $this->calculateUnitBuildPrice($project, $number_of_builds, $currency) |
| 227 | + ?->toScale(2, RoundingMode::UP); |
| 228 | + } |
| 229 | + |
| 230 | + /** |
| 231 | + * Returns the effective unit price for a single piece of the given BOM entry, |
| 232 | + * taking bulk pricing and minimum order amounts into account for N builds. |
| 233 | + * Returns BigDecimal::zero() when no pricing data is available. |
| 234 | + */ |
| 235 | + public function getEntryUnitPrice(ProjectBOMEntry $entry, int $number_of_builds = 1, ?Currency $currency = null): BigDecimal |
| 236 | + { |
| 237 | + return $this->getBomEntryUnitPrice($entry, $number_of_builds, $currency) ?? BigDecimal::zero(); |
| 238 | + } |
| 239 | + |
| 240 | + /** |
| 241 | + * Returns the effective unit price for a single piece of the given BOM entry, |
| 242 | + * taking bulk pricing into account for N builds. |
| 243 | + */ |
| 244 | + private function getBomEntryUnitPrice(ProjectBOMEntry $entry, int $number_of_builds, ?Currency $currency): ?BigDecimal |
| 245 | + { |
| 246 | + if ($entry->getPart() instanceof Part) { |
| 247 | + $total_qty = $entry->getQuantity() * $number_of_builds; |
| 248 | + $min_order = $this->pricedetailHelper->getMinOrderAmount($entry->getPart()); |
| 249 | + $effective_qty = ($min_order !== null) ? max($total_qty, $min_order) : $total_qty; |
| 250 | + return $this->pricedetailHelper->calculateAvgPrice($entry->getPart(), $effective_qty, $currency); |
| 251 | + } |
| 252 | + return $entry->getPrice(); |
| 253 | + } |
171 | 254 | } |
0 commit comments