Use BigDecimal for Currency exchange rate.

This commit is contained in:
Jan Böhmer 2020-05-20 20:57:16 +02:00
parent 08267b88b0
commit db4d7dc5fc
9 changed files with 135 additions and 20 deletions

View file

@ -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;

View file

@ -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
*/

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -0,0 +1,59 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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;
}
}

View file

@ -0,0 +1,49 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 <jan.schaedlich@sensiolabs.de>
*/
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;
}
}

View file

@ -20,7 +20,7 @@
{{ form_row(form.exchange_rate) }}
{% if entity.inverseExchangeRate %}
<span class="form-text text-muted offset-3 col-9">
{{ '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}) }}
</span>
{% endif %}
{% endblock %}

View file

@ -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());
}
}