diff --git a/composer.json b/composer.json index dd132b69..a1f15834 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "api-platform/json-api": "^4.0.0", "api-platform/symfony": "^4.0.0", "beberlei/doctrineextensions": "^1.2", - "brick/math": "^0.14.8", + "brick/math": "^0.17.0", "brick/schema": "^0.2.0", "composer/ca-bundle": "^1.5", "composer/package-versions-deprecated": "^1.11.99.5", diff --git a/composer.lock b/composer.lock index f6bb2f24..101cedac 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f7dc75ebd6fa0d9c496a32ea984fa4a5", + "content-hash": "d6bda397c505e1e6d540c814a2368fbb", "packages": [ { "name": "amphp/amp", @@ -2342,23 +2342,22 @@ }, { "name": "brick/math", - "version": "0.14.8", + "version": "0.17.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + "reference": "6aef71a9fbbd1ee7be0e313cd627f8e6f7125a5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "url": "https://api.github.com/repos/brick/math/zipball/6aef71a9fbbd1ee7be0e313cd627f8e6f7125a5b", + "reference": "6aef71a9fbbd1ee7be0e313cd627f8e6f7125a5b", "shasum": "" }, "require": { "php": "^8.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", "phpstan/phpstan": "2.1.22", "phpunit/phpunit": "^11.5" }, @@ -2390,7 +2389,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.8" + "source": "https://github.com/brick/math/tree/0.17.1" }, "funding": [ { @@ -2398,7 +2397,7 @@ "type": "github" } ], - "time": "2026-02-10T14:33:43+00:00" + "time": "2026-04-19T20:55:20+00:00" }, { "name": "brick/schema", @@ -4637,16 +4636,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.10.0", + "version": "7.10.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "reference": "b777df1776c667e287664dda75b0298ad8ae3a14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b777df1776c667e287664dda75b0298ad8ae3a14", + "reference": "b777df1776c667e287664dda75b0298ad8ae3a14", "shasum": "" }, "require": { @@ -4664,8 +4663,9 @@ "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "guzzle/client-integration-tests": "3.0.2", + "guzzlehttp/test-server": "^0.3.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -4743,7 +4743,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "source": "https://github.com/guzzle/guzzle/tree/7.10.1" }, "funding": [ { @@ -4759,20 +4759,20 @@ "type": "tidelift" } ], - "time": "2025-08-23T22:36:01+00:00" + "time": "2026-05-19T18:01:31+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "481557b130ef3790cf82b713667b43030dc9c957" + "reference": "d2d8dfae4757f384d630fdffc2d8d6618d8f4c5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", - "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "url": "https://api.github.com/repos/guzzle/promises/zipball/d2d8dfae4757f384d630fdffc2d8d6618d8f4c5e", + "reference": "d2d8dfae4757f384d630fdffc2d8d6618d8f4c5e", "shasum": "" }, "require": { @@ -4780,7 +4780,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "type": "library", "extra": { @@ -4826,7 +4826,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.3.0" + "source": "https://github.com/guzzle/promises/tree/2.3.1" }, "funding": [ { @@ -4842,20 +4842,20 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:34:08+00:00" + "time": "2026-05-19T18:30:48+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.9.1", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "3780f78d6f2854cb327944a22c7b0617852ab7e9" + "reference": "d5ddaf5743c42a61cb6100f83dc9d5a2bafe75ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/3780f78d6f2854cb327944a22c7b0617852ab7e9", - "reference": "3780f78d6f2854cb327944a22c7b0617852ab7e9", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/d5ddaf5743c42a61cb6100f83dc9d5a2bafe75ca", + "reference": "d5ddaf5743c42a61cb6100f83dc9d5a2bafe75ca", "shasum": "" }, "require": { @@ -4943,7 +4943,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.9.1" + "source": "https://github.com/guzzle/psr7/tree/2.10.0" }, "funding": [ { @@ -4959,7 +4959,7 @@ "type": "tidelift" } ], - "time": "2026-05-19T15:17:22+00:00" + "time": "2026-05-19T17:32:11+00:00" }, { "name": "hshn/base64-encoded-file", diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index 2d5c4ebc..b5beeca0 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -37,6 +37,7 @@ use App\Services\EntityURLGenerator; use App\Services\Formatters\AmountFormatter; use App\Services\Formatters\MoneyFormatter; use App\Services\ProjectSystem\ProjectBuildHelper; +use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\Query; @@ -93,14 +94,14 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface return htmlspecialchars($this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit())); }, ]) - ->add('partId', TextColumn::class, [ - 'label' => $this->translator->trans('project.bom.part_id'), - 'visible' => true, - 'orderField' => 'part.id', - 'render' => function ($value, ProjectBOMEntry $context) { - return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : ''; - }, - ]) + ->add('partId', TextColumn::class, [ + 'label' => $this->translator->trans('project.bom.part_id'), + 'visible' => true, + 'orderField' => 'part.id', + 'render' => function ($value, ProjectBOMEntry $context) { + return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : ''; + }, + ]) ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), 'orderField' => 'NATSORT(part.name)', @@ -161,7 +162,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => $this->translator->trans('part.table.manufacturingStatus'), 'data' => static fn(ProjectBOMEntry $context): ?ManufacturingStatus => $context->getPart()?->getManufacturingStatus(), 'orderField' => 'part.manufacturing_status', - 'class' => ManufacturingStatus::class, + 'class' => ManufacturingStatus::class, 'render' => function (?ManufacturingStatus $status, ProjectBOMEntry $context): string { if ($status === null) { return ''; @@ -212,7 +213,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { $price = $this->projectBuildHelper->getEntryUnitPrice($context); - return $this->moneyFormatter->format($price->toScale(2, RoundingMode::UP)->toFloat(), null, 2, true); + return $this->moneyFormatter->format($price->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true); }, ]) ->add('ext_price', TextColumn::class, [ @@ -221,7 +222,8 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'render' => function ($value, ProjectBOMEntry $context) { $price = $this->projectBuildHelper->getEntryUnitPrice($context); return $this->moneyFormatter->format( - $price->multipliedBy($context->getQuantity())->toScale(2, RoundingMode::UP)->toFloat(), + $price->multipliedBy(BigDecimal::fromFloatShortest($context->getQuantity())) + ->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true diff --git a/src/Doctrine/Types/BigDecimalType.php b/src/Doctrine/Types/BigDecimalType.php index a9f796dd..2712cce8 100644 --- a/src/Doctrine/Types/BigDecimalType.php +++ b/src/Doctrine/Types/BigDecimalType.php @@ -44,7 +44,7 @@ class BigDecimalType extends Type - return BigDecimal::of($value); + return BigDecimal::of(is_float($value) ? BigDecimal::fromFloatShortest($value) : $value); } /** diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index ce20caf8..4a811aa0 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -204,7 +204,7 @@ class Currency extends AbstractStructuralDBElement return null; } - return BigDecimal::one()->dividedBy($tmp, $tmp->getScale(), RoundingMode::HALF_UP); + return BigDecimal::one()->dividedBy($tmp, $tmp->getScale(), RoundingMode::HalfUp); } /** diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 553b07a3..58e02c64 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -195,10 +195,10 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface #[SerializedName('price_per_unit')] public function getPricePerUnit(float|string|BigDecimal $multiplier = 1.0): BigDecimal { - $tmp = BigDecimal::of($multiplier); + $tmp = is_float($multiplier) ? BigDecimal::fromFloatShortest($multiplier) : BigDecimal::of($multiplier); $tmp = $tmp->multipliedBy($this->price); - return $tmp->dividedBy($this->price_related_quantity, static::PRICE_PRECISION, RoundingMode::HALF_UP); + return $tmp->dividedBy(BigDecimal::fromFloatShortest($this->price_related_quantity), static::PRICE_PRECISION, RoundingMode::HalfUp); } /** @@ -317,7 +317,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface */ public function setPrice(BigDecimal $new_price): self { - $tmp = $new_price->toScale(self::PRICE_PRECISION, RoundingMode::HALF_UP); + $tmp = $new_price->toScale(self::PRICE_PRECISION, RoundingMode::HalfUp); //Only change the object, if the value changes, so that doctrine does not detect it as changed. if ((string) $tmp !== (string) $this->price) { $this->price = $tmp; diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index cab5a49b..e63ec7f1 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -286,7 +286,7 @@ class PKPartImporter //Partkeepr stores the price per item, we need to convert it to the price per packaging unit $price_per_item = BigDecimal::of($partdistributor['price']); $packaging_unit = (float) ($partdistributor['packagingUnit'] ?? 1); - $pricedetail->setPrice($price_per_item->multipliedBy($packaging_unit)); + $pricedetail->setPrice($price_per_item->multipliedBy(BigDecimal::fromFloatShortest($packaging_unit))); $pricedetail->setPriceRelatedQuantity($packaging_unit); //We have to set the minimum discount quantity to the packaging unit (PartKeepr does not know this concept) //But in Part-DB the minimum discount qty have to be unique across a orderdetail diff --git a/src/Services/LogSystem/TimeTravel.php b/src/Services/LogSystem/TimeTravel.php index 68d962bb..79d9f8e2 100644 --- a/src/Services/LogSystem/TimeTravel.php +++ b/src/Services/LogSystem/TimeTravel.php @@ -222,7 +222,7 @@ class TimeTravel if (isset($metadata->fieldMappings[$field])) { //We need to convert the string to a BigDecimal first if (!$data instanceof BigDecimal && ('big_decimal' === $metadata->getFieldMapping($field)->type)) { - $data = BigDecimal::of($data); + $data = is_float($data) ? BigDecimal::fromFloatShortest($data) : BigDecimal::of($data); } if (!$data instanceof \DateTimeInterface diff --git a/src/Services/Parts/PricedetailHelper.php b/src/Services/Parts/PricedetailHelper.php index b2e1340f..e40fc44d 100644 --- a/src/Services/Parts/PricedetailHelper.php +++ b/src/Services/Parts/PricedetailHelper.php @@ -170,7 +170,7 @@ class PricedetailHelper return null; } - return $avg->dividedBy($count, Pricedetail::PRICE_PRECISION, RoundingMode::HALF_UP); + return $avg->dividedBy($count, Pricedetail::PRICE_PRECISION, RoundingMode::HalfUp); } /** @@ -213,6 +213,6 @@ class PricedetailHelper $val_target = $val_base->multipliedBy($targetCurrency->getInverseExchangeRate()); } - return $val_target->toScale(Pricedetail::PRICE_PRECISION, RoundingMode::HALF_UP); + return $val_target->toScale(Pricedetail::PRICE_PRECISION, RoundingMode::HalfUp); } } diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index ee5b8c68..e011d980 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -190,7 +190,7 @@ final readonly class ProjectBuildHelper continue; } $has_price = true; - $total = $total->plus($unit_price->multipliedBy($entry->getQuantity())->multipliedBy($number_of_builds)); + $total = $total->plus($unit_price->multipliedBy(BigDecimal::fromFloatShortest($entry->getQuantity()))->multipliedBy($number_of_builds)); } return $has_price ? $total : null; @@ -206,7 +206,7 @@ final readonly class ProjectBuildHelper if ($total === null) { return null; } - return $total->dividedBy($number_of_builds, 10, RoundingMode::HALF_UP); + return $total->dividedBy($number_of_builds, 10, RoundingMode::HalfUp); } /** @@ -215,7 +215,7 @@ final readonly class ProjectBuildHelper public function roundedTotalBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal { return $this->calculateTotalBuildPrice($project, $number_of_builds, $currency) - ?->toScale(2, RoundingMode::UP); + ?->toScale(2, RoundingMode::Up); } /** @@ -224,7 +224,7 @@ final readonly class ProjectBuildHelper public function roundedUnitBuildPrice(Project $project, int $number_of_builds = 1, ?Currency $currency = null): ?BigDecimal { return $this->calculateUnitBuildPrice($project, $number_of_builds, $currency) - ?->toScale(2, RoundingMode::UP); + ?->toScale(2, RoundingMode::Up); } /** diff --git a/src/Services/Tools/ExchangeRateUpdater.php b/src/Services/Tools/ExchangeRateUpdater.php index 6eb7ec13..ccc3f19f 100644 --- a/src/Services/Tools/ExchangeRateUpdater.php +++ b/src/Services/Tools/ExchangeRateUpdater.php @@ -44,15 +44,15 @@ class ExchangeRateUpdater try { //Try it in the direction QUOTE/BASE first, as most providers provide rates in this direction $rate = $this->swap->latest($currency->getIsoCode().'/'.$this->localizationSettings->baseCurrency); - $effective_rate = BigDecimal::of($rate->getValue()); + $effective_rate = BigDecimal::fromFloatShortest($rate->getValue()); } catch (UnsupportedCurrencyPairException|UnsupportedExchangeQueryException $exception) { //Otherwise try to get it inverse and calculate it ourselfes, from the format "BASE/QUOTE" $rate = $this->swap->latest($this->localizationSettings->baseCurrency.'/'.$currency->getIsoCode()); //The rate says how many quote units are worth one base unit //So we need to invert it to get the exchange rate - $rate_bd = BigDecimal::of($rate->getValue()); - $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); + $rate_bd = BigDecimal::fromFloatShortest($rate->getValue()); + $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HalfUp); } $currency->setExchangeRate($effective_rate);