diff --git a/.travis.yml b/.travis.yml index 65c9af6c..74eb620d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ matrix: before_install: - phpenv config-rm xdebug.ini - composer self-update + - php -i install: - composer install diff --git a/composer.json b/composer.json index 1d8c5ff4..9c786813 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "symfony/flex": "^1.0", "symfony/form": "^4.0", "symfony/framework-bundle": "^4.0", + "symfony/intl": "^4.0", "symfony/maker-bundle": "^1.0", "symfony/monolog-bundle": "^3.1", "symfony/orm-pack": "^1.0", diff --git a/src/Invoice/Renderer/AbstractRenderer.php b/src/Invoice/Renderer/AbstractRenderer.php index d7a3dbcb..65b53e70 100644 --- a/src/Invoice/Renderer/AbstractRenderer.php +++ b/src/Invoice/Renderer/AbstractRenderer.php @@ -55,11 +55,12 @@ abstract class AbstractRenderer /** * @param $amount - * @return mixed + * @param $currency + * @return string */ - protected function getFormattedMoney($amount) + protected function getFormattedMoney($amount, $currency) { - return $this->extension->money($amount); + return $this->extension->money($amount, $currency); } /** diff --git a/src/Invoice/Renderer/RendererTrait.php b/src/Invoice/Renderer/RendererTrait.php index 98f3f6ec..2a51c0a8 100644 --- a/src/Invoice/Renderer/RendererTrait.php +++ b/src/Invoice/Renderer/RendererTrait.php @@ -53,7 +53,7 @@ trait RendererTrait * @param $amount * @return mixed */ - abstract protected function getFormattedMoney($amount); + abstract protected function getFormattedMoney($amount, $currency); /** * @param \DateTime $date @@ -75,6 +75,7 @@ trait RendererTrait { $customer = $model->getCustomer(); $project = $model->getQuery()->getProject(); + $currency = $model->getCalculator()->getCurrency(); $values = [ 'invoice.due_date' => $this->getFormattedDateTime($model->getDueDate()), @@ -82,10 +83,10 @@ trait RendererTrait 'invoice.number' => $model->getNumberGenerator()->getInvoiceNumber(), 'invoice.currency' => $model->getCalculator()->getCurrency(), 'invoice.vat' => $model->getCalculator()->getVat(), - 'invoice.tax' => $this->getFormattedMoney($model->getCalculator()->getTax()), + 'invoice.tax' => $this->getFormattedMoney($model->getCalculator()->getTax(), $currency), 'invoice.total_time' => $this->getFormattedDuration($model->getCalculator()->getTimeWorked()), - 'invoice.total' => $this->getFormattedMoney($model->getCalculator()->getTotal()), - 'invoice.subtotal' => $this->getFormattedMoney($model->getCalculator()->getSubtotal()), + 'invoice.total' => $this->getFormattedMoney($model->getCalculator()->getTotal(), $currency), + 'invoice.subtotal' => $this->getFormattedMoney($model->getCalculator()->getSubtotal(), $currency), 'template.name' => $model->getTemplate()->getName(), 'template.company' => $model->getTemplate()->getCompany(), @@ -156,6 +157,7 @@ trait RendererTrait $activity = $timesheet->getActivity(); $project = $timesheet->getProject(); $customer = $project->getCustomer(); + $currency = $customer->getCurrency(); $begin = $timesheet->getBegin(); $end = $timesheet->getEnd(); @@ -164,8 +166,9 @@ trait RendererTrait 'entry.row' => '', 'entry.description' => $description, 'entry.amount' => $amount, - 'entry.rate' => $this->getFormattedMoney($hourlyRate), - 'entry.total' => $this->getFormattedMoney($rate), + 'entry.rate' => $this->getFormattedMoney($hourlyRate, $currency), + 'entry.total' => $this->getFormattedMoney($rate, $currency), + 'entry.currency' => $currency, 'entry.duration' => $timesheet->getDuration(), 'entry.duration_minutes' => number_format($timesheet->getDuration() / 60), 'entry.begin' => $this->getFormattedDateTime($begin), diff --git a/src/Twig/Extensions.php b/src/Twig/Extensions.php index 67fe6e0c..274db78e 100644 --- a/src/Twig/Extensions.php +++ b/src/Twig/Extensions.php @@ -254,7 +254,7 @@ class Extensions extends \Twig_Extension if ($this->locale !== $locale) { $this->locale = $locale; - $this->numberFormatter = new NumberFormatter($locale, NumberFormatter::DECIMAL); + $this->numberFormatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); } $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits($currency); @@ -262,7 +262,7 @@ class Extensions extends \Twig_Extension $result = $this->numberFormatter->format($amount); if (null !== $currency) { - $result .= ' ' . Intl::getCurrencyBundle()->getCurrencySymbol($currency, $locale); + $result = $this->numberFormatter->formatCurrency($amount, $currency); } return $result; diff --git a/tests/Export/Renderer/CsvRendererTest.php b/tests/Export/Renderer/CsvRendererTest.php index aae2c643..14b13244 100644 --- a/tests/Export/Renderer/CsvRendererTest.php +++ b/tests/Export/Renderer/CsvRendererTest.php @@ -31,7 +31,7 @@ class CsvRendererTest extends AbstractRendererTest public function getTestModel() { return [ - ['01:50 h', '2,437.12 €', '1,947.99 €', 7, 5, 1, 2, 2] + ['01:50 h', '€2,437.12', '€1,947.99', 7, 5, 1, 2, 2] ]; } diff --git a/tests/Export/Renderer/HtmlRendererTest.php b/tests/Export/Renderer/HtmlRendererTest.php index 982315ba..48af8dd2 100644 --- a/tests/Export/Renderer/HtmlRendererTest.php +++ b/tests/Export/Renderer/HtmlRendererTest.php @@ -55,7 +55,7 @@ class HtmlRendererTest extends AbstractRendererTest $this->assertContains('Customer Name', $content); $this->assertContains('project name', $content); $this->assertContains('01:50 h', $content); - $this->assertContains('2,437.12 €', $content); + $this->assertContains('€2,437.12', $content); $this->assertEquals(5, substr_count($content, 'activity description')); } diff --git a/tests/Invoice/Renderer/AbstractRendererTest.php b/tests/Invoice/Renderer/AbstractRendererTest.php index 37cfc84e..2ec30b9d 100644 --- a/tests/Invoice/Renderer/AbstractRendererTest.php +++ b/tests/Invoice/Renderer/AbstractRendererTest.php @@ -60,7 +60,7 @@ abstract class AbstractRendererTest extends KernelTestCase $languages = [ 'en' => [ 'date' => 'Y.m.d', - 'duration' => '%%h:%%m h' + 'duration' => '%h:%m h' ] ]; @@ -83,6 +83,7 @@ abstract class AbstractRendererTest extends KernelTestCase protected function getInvoiceModel() { $customer = new Customer(); + $customer->setCurrency('EUR'); $template = new InvoiceTemplate(); $template->setTitle('a test invoice template title'); $template->setVat(19); @@ -194,6 +195,7 @@ abstract class AbstractRendererTest extends KernelTestCase protected function getInvoiceModelOneEntry() { $customer = new Customer(); + $customer->setCurrency('USD'); $template = new InvoiceTemplate(); $template->setTitle('a test invoice template title'); $template->setVat(19); diff --git a/tests/Invoice/Renderer/CsvRendererTest.php b/tests/Invoice/Renderer/CsvRendererTest.php index 822804da..d57ab204 100644 --- a/tests/Invoice/Renderer/CsvRendererTest.php +++ b/tests/Invoice/Renderer/CsvRendererTest.php @@ -36,8 +36,8 @@ class CsvRendererTest extends AbstractRendererTest public function getTestModel() { - yield [$this->getInvoiceModel(), '1,947.99', 6, 5, 1, 2, 2]; - yield [$this->getInvoiceModelOneEntry(), '293.27', 2, 1, 0, 1, 0]; + yield [$this->getInvoiceModel(), '€1,947.99', 6, 5, 1, 2, 2]; + yield [$this->getInvoiceModelOneEntry(), '$293.27', 2, 1, 0, 1, 0]; } /** diff --git a/tests/Invoice/Renderer/DebugRendererTest.php b/tests/Invoice/Renderer/DebugRendererTest.php index b042b809..d001fe96 100644 --- a/tests/Invoice/Renderer/DebugRendererTest.php +++ b/tests/Invoice/Renderer/DebugRendererTest.php @@ -100,6 +100,7 @@ class DebugRendererTest extends AbstractRendererTest 'entry.amount', 'entry.rate', 'entry.total', + 'entry.currency', 'entry.duration', 'entry.duration_minutes', 'entry.begin', diff --git a/tests/Twig/ExtensionsTest.php b/tests/Twig/ExtensionsTest.php index fa324fd8..f829e539 100644 --- a/tests/Twig/ExtensionsTest.php +++ b/tests/Twig/ExtensionsTest.php @@ -15,6 +15,7 @@ use App\Utils\LocaleSettings; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Intl\Util\IntlTestHelper; use Twig\TwigFilter; /** @@ -112,10 +113,6 @@ class ExtensionsTest extends TestCase } /** - * @param string $result - * @param int $amount - * @param string $currency - * @param string $locale * @dataProvider getMoneyData */ public function testMoney($result, $amount, $currency, $locale) @@ -127,19 +124,36 @@ class ExtensionsTest extends TestCase public function getMoneyData() { return [ - ['0 €', null, 'EUR', 'de'], - ['2.345 €', 2345, 'EUR', 'de'], - ['2,345 €', 2345, 'EUR', 'en'], - ['2,345.01 €', 2345.009, 'EUR', 'en'], - ['2.345,01 €', 2345.009, 'EUR', 'de'], - ['13.75 $', 13.75, 'USD', 'en'], - ['13,75 $', 13.75, 'USD', 'de'], - ['13,75 RUB', 13.75, 'RUB', 'de'], - ['13,5 RUB', 13.50, 'RUB', 'de'], - ['13,75 ₽', 13.75, 'RUB', 'ru'], - ['14 ¥', 13.75, 'JPY', 'de'], - ['13 933 ¥', 13933.49, 'JPY', 'ru'], - ['1.234.567,89 $', 1234567.891234567890000, 'USD', 'de'], + ['0,00 €', null, 'EUR', 'de'], + ['2.345,00 €', 2345, 'EUR', 'de'], + ['€2,345.00', 2345, 'EUR', 'en'], + ['€2,345.01', 2345.009, 'EUR', 'en'], + ['2.345,01 €', 2345.009, 'EUR', 'de'], + ['$13.75', 13.75, 'USD', 'en'], + ['13,75 $', 13.75, 'USD', 'de'], + ['13,75 RUB', 13.75, 'RUB', 'de'], + ['14 ¥', 13.75, 'JPY', 'de'], + ['13 933 ¥', 13933.49, 'JPY', 'ru'], + ['1.234.567,89 $', 1234567.891234567890000, 'USD', 'de'], + ]; + } + + /** + * @dataProvider getMoneyData62_1 + */ + public function testMoney62_1($result, $amount, $currency, $locale) + { + IntlTestHelper::requireFullIntl($this, '62.1'); + + $sut = $this->getSut($this->localeEn, $locale); + $this->assertEquals($result, $sut->money($amount, $currency)); + } + + public function getMoneyData62_1() + { + return [ + ['RUB 13.50', 13.50, 'RUB', 'en'], + ['13,75 ₽', 13.75, 'RUB', 'ru'], ]; } diff --git a/var/docs/invoices.md b/var/docs/invoices.md index a2aafe42..c8b2a300 100644 --- a/var/docs/invoices.md +++ b/var/docs/invoices.md @@ -114,6 +114,7 @@ The documents which are rendered passively (ODS, XLSX, CSV, DOCX) can use the fo | ${invoice.total_time} | The total working time (entries with a fixed rate are always calculated with 1) | | ${invoice.total} | The invoices total (including tax) | | ${invoice.subtotal} | The invoices subtotal (excluding tax) | +| ${invoice.currency} | The invoices currency as string (like EUR or USD) | | ${invoice.vat} | The VAT in percent for this invoice | | ${invoice.tax} | The tax of the invoice amount | | ${template.name} | The invoice name, as configured in your template | @@ -161,6 +162,7 @@ For each timesheet entry you can use the variables from the following table. | ${entry.amount} | The format duration/amount for this entry | 02:47 h | | ${entry.rate} | The rate for one unit of the entry (normally one hour) | 100 | | ${entry.total} | The total rate for this entry | 278,33 | +| ${entry.currency} | The currency for this record as string (like EUR or USD) | EUR | | ${entry.duration} | The duration in seconds | 10020 | | ${entry.duration_minutes} | The duration in minutes with no decimals | 167 | | ${entry.begin} | The begin date (format depends on the users language) | 27.10.2018 |