diff --git a/src/Command/UpdateExchangeRatesCommand.php b/src/Command/UpdateExchangeRatesCommand.php index 9e06669a..9f52e124 100644 --- a/src/Command/UpdateExchangeRatesCommand.php +++ b/src/Command/UpdateExchangeRatesCommand.php @@ -43,6 +43,7 @@ declare(strict_types=1); namespace App\Command; use App\Entity\PriceInformations\Currency; +use Brick\Math\BigDecimal; use function count; use Doctrine\ORM\EntityManagerInterface; use Exchanger\Exception\Exception; @@ -122,10 +123,11 @@ class UpdateExchangeRatesCommand extends Command //Iterate over each candidate and update exchange rate foreach ($candidates as $currency) { + /** @var Currency $currency */ try { $rate = $swap->latest($currency->getIsoCode().'/'.$this->base_current); - $currency->setExchangeRate((string) $rate->getValue()); - $io->note(sprintf('Set exchange rate of %s to %f', $currency->getIsoCode(), $currency->getExchangeRate())); + $currency->setExchangeRate(BigDecimal::of($rate->getValue())); + $io->note(sprintf('Set exchange rate of %s to %f', $currency->getIsoCode(), $currency->getExchangeRate()->toFloat())); $this->em->persist($currency); ++$success_counter; diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index 7bf16083..e731c940 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -153,7 +153,7 @@ class Supplier extends AbstractCompany /** * Sets the shipping costs for an order with this supplier. * - * @param string|null $shipping_costs a BigDecimal with the shipping costs + * @param BigDecimal|null $shipping_costs a BigDecimal with the shipping costs * * @return Supplier */ diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 300bf0e0..90aa8f25 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -45,6 +45,9 @@ namespace App\Entity\PriceInformations; use App\Entity\Attachments\CurrencyAttachment; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parameters\CurrencyParameter; +use App\Validator\Constraints\BigDecimal\BigDecimalPositive; +use Brick\Math\BigDecimal; +use Brick\Math\RoundingMode; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -63,10 +66,10 @@ class Currency extends AbstractStructuralDBElement public const PRICE_SCALE = 5; /** - * @var string|null The exchange rate between this currency and the base currency + * @var BigDecimal|null The exchange rate between this currency and the base currency * (how many base units the current currency is worth) - * @ORM\Column(type="decimal", precision=11, scale=5, nullable=true) - * @Assert\Positive() + * @ORM\Column(type="big_decimal", precision=11, scale=5, nullable=true) + * @BigDecimalPositive() */ protected $exchange_rate; @@ -148,22 +151,22 @@ class Currency extends AbstractStructuralDBElement /** * Returns the inverse exchange rate (how many of the current currency the base unit is worth). */ - public function getInverseExchangeRate(): ?string + public function getInverseExchangeRate(): ?BigDecimal { $tmp = $this->getExchangeRate(); - if (null === $tmp || '0' === $tmp) { + if (null === $tmp || $tmp->isZero()) { return null; } - return bcdiv('1', $tmp, static::PRICE_SCALE); + return BigDecimal::one()->dividedBy($tmp, $tmp->getScale(), RoundingMode::HALF_UP); } /** * Returns The exchange rate between this currency and the base currency * (how many base units the current currency is worth). */ - public function getExchangeRate(): ?string + public function getExchangeRate(): ?BigDecimal { return $this->exchange_rate; } @@ -171,12 +174,12 @@ class Currency extends AbstractStructuralDBElement /** * Sets the exchange rate of the currency. * - * @param string|null $exchange_rate The new exchange rate of the currency. + * @param BigDecimal|null $exchange_rate The new exchange rate of the currency. * Set to null, if the exchange rate is unknown. * * @return Currency */ - public function setExchangeRate(?string $exchange_rate): self + public function setExchangeRate(?BigDecimal $exchange_rate): self { $this->exchange_rate = $exchange_rate; diff --git a/src/Form/AdminPages/CurrencyAdminForm.php b/src/Form/AdminPages/CurrencyAdminForm.php index 6f3e5a3c..cb53ca90 100644 --- a/src/Form/AdminPages/CurrencyAdminForm.php +++ b/src/Form/AdminPages/CurrencyAdminForm.php @@ -43,6 +43,7 @@ declare(strict_types=1); namespace App\Form\AdminPages; use App\Entity\Base\AbstractNamedDBElement; +use App\Form\Type\BigDecimalMoneyType; use Symfony\Component\Form\Extension\Core\Type\CurrencyType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; use Symfony\Component\Form\FormBuilderInterface; @@ -74,7 +75,7 @@ class CurrencyAdminForm extends BaseEntityAdminForm 'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]); - $builder->add('exchange_rate', MoneyType::class, [ + $builder->add('exchange_rate', BigDecimalMoneyType::class, [ 'required' => false, 'label' => 'currency.edit.exchange_rate', 'currency' => $this->default_currency, diff --git a/src/Services/PricedetailHelper.php b/src/Services/PricedetailHelper.php index ac480c14..0da8eb31 100644 --- a/src/Services/PricedetailHelper.php +++ b/src/Services/PricedetailHelper.php @@ -213,11 +213,11 @@ class PricedetailHelper $val_base = $value; if (null !== $originCurrency) { //Without an exchange rate we can not calculate the exchange rate - if (0.0 === (float) $originCurrency->getExchangeRate()) { + if ($originCurrency->getExchangeRate() === null || $originCurrency->getExchangeRate()->isZero()) { return null; } - $val_base = bcmul($value, $originCurrency->getExchangeRate(), Pricedetail::PRICE_PRECISION); + $val_base = bcmul($value, (string) $originCurrency->getExchangeRate(), Pricedetail::PRICE_PRECISION); } //Convert value in base currency to target currency @@ -228,7 +228,7 @@ class PricedetailHelper return null; } - $val_target = bcmul($val_base, $targetCurrency->getInverseExchangeRate(), Pricedetail::PRICE_PRECISION); + $val_target = bcmul($val_base, (string) $targetCurrency->getInverseExchangeRate(), Pricedetail::PRICE_PRECISION); } return $val_target; diff --git a/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThanValidator.php b/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThanValidator.php new file mode 100644 index 00000000..e140223c --- /dev/null +++ b/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThanValidator.php @@ -0,0 +1,59 @@ +. + */ + +namespace App\Validator\Constraints\BigDecimal; + + +use Brick\Math\BigDecimal; +use Symfony\Component\Validator\Constraints\AbstractComparisonValidator; +use Symfony\Component\Validator\Constraints\GreaterThan; + +/** + * Validates values are greater than the previous (>). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class BigDecimalGreaterThanValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + if ($value1 instanceof BigDecimal) { + $value1 = (string) $value1; + } + + if ($value2 instanceof BigDecimal) { + $value2 = (string) $value2; + } + + return null === $value2 || $value1 > $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return GreaterThan::TOO_LOW_ERROR; + } +} \ No newline at end of file diff --git a/src/Validator/Constraints/BigDecimal/BigDecimalPositive.php b/src/Validator/Constraints/BigDecimal/BigDecimalPositive.php new file mode 100644 index 00000000..aa92a3ab --- /dev/null +++ b/src/Validator/Constraints/BigDecimal/BigDecimalPositive.php @@ -0,0 +1,49 @@ +. + */ + +namespace App\Validator\Constraints\BigDecimal; + + +use Symfony\Component\Validator\Constraints\GreaterThan; +use Symfony\Component\Validator\Constraints\GreaterThanValidator; +use Symfony\Component\Validator\Constraints\NumberConstraintTrait; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Jan Schädlich + */ +class BigDecimalPositive extends GreaterThan +{ + use NumberConstraintTrait; + + public $message = 'This value should be positive.'; + + public function __construct($options = null) + { + parent::__construct($this->configureNumberConstraintOptions($options)); + } + + public function validatedBy(): string + { + return BigDecimalGreaterThanValidator::class; + } +} \ No newline at end of file diff --git a/templates/AdminPages/CurrencyAdmin.html.twig b/templates/AdminPages/CurrencyAdmin.html.twig index ed50f108..43f443d3 100644 --- a/templates/AdminPages/CurrencyAdmin.html.twig +++ b/templates/AdminPages/CurrencyAdmin.html.twig @@ -20,7 +20,7 @@ {{ form_row(form.exchange_rate) }} {% if entity.inverseExchangeRate %} - {{ '1'|format_currency(default_currency) }} = {{ entity.inverseExchangeRate | format_currency(entity.isoCode, {fraction_digit: 5}) }} + {{ '1'|format_currency(default_currency) }} = {{ entity.inverseExchangeRate.tofloat | format_currency(entity.isoCode, {fraction_digit: 5}) }} {% endif %} {% endblock %} diff --git a/tests/Entity/PriceSystem/CurrencyTest.php b/tests/Entity/PriceSystem/CurrencyTest.php index 810f51ac..07a7e030 100644 --- a/tests/Entity/PriceSystem/CurrencyTest.php +++ b/tests/Entity/PriceSystem/CurrencyTest.php @@ -43,6 +43,7 @@ declare(strict_types=1); namespace App\Tests\Entity\PriceSystem; use App\Entity\PriceInformations\Currency; +use Brick\Math\BigDecimal; use PHPUnit\Framework\TestCase; class CurrencyTest extends TestCase @@ -54,10 +55,10 @@ class CurrencyTest extends TestCase //By default the inverse exchange rate is not set: $this->assertNull($currency->getInverseExchangeRate()); - $currency->setExchangeRate('0'); + $currency->setExchangeRate(BigDecimal::zero()); $this->assertNull($currency->getInverseExchangeRate()); - $currency->setExchangeRate('1.45643'); - $this->assertSame('0.68661', $currency->getInverseExchangeRate()); + $currency->setExchangeRate(BigDecimal::of('1.45643')); + $this->assertSame(BigDecimal::of('0.68661'), $currency->getInverseExchangeRate()); } }