invoices: unified money, number and date formats and fully respect configured language (#1814)

This commit is contained in:
Kevin Papst
2020-07-10 15:09:52 +02:00
committed by GitHub
parent 19e4ebf88c
commit 32c1e3258e
67 changed files with 1593 additions and 2335 deletions

View File

@@ -10,8 +10,7 @@
namespace App\Invoice;
use App\Twig\DateExtensions;
use App\Twig\Extensions;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Twig\LocaleExtensions;
final class DefaultInvoiceFormatter implements InvoiceFormatter
{
@@ -19,25 +18,13 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter
* @var DateExtensions
*/
private $dateExtension;
/**
* @var Extensions
* @var LocaleExtensions
*/
private $extension;
/**
* @var TranslatorInterface
*/
private $translator;
/**
* @param TranslatorInterface $translator
* @param DateExtensions $dateExtension
* @param Extensions $extensions
*/
public function __construct(TranslatorInterface $translator, DateExtensions $dateExtension, Extensions $extensions)
public function __construct(DateExtensions $dateExtension, LocaleExtensions $extensions)
{
$this->translator = $translator;
$this->dateExtension = $dateExtension;
$this->extension = $extensions;
}
@@ -60,23 +47,24 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter
return $this->dateExtension->time($date);
}
/**
* @param int $amount
* @param string $currency
* @return string
*/
public function getFormattedMoney($amount, $currency)
{
return $this->extension->money($amount, $currency);
}
/**
* @param \DateTime $date
* @return mixed
*/
public function getFormattedMonthName(\DateTime $date)
{
return $this->translator->trans($this->dateExtension->monthName($date));
return $this->dateExtension->monthName($date);
}
/**
* @param float|int $amount
* @param string|null $currency
* @param bool $withCurrency
* @return string
*/
public function getFormattedMoney($amount, ?string $currency, bool $withCurrency = true)
{
return $this->extension->money($amount, $currency, $withCurrency);
}
/**

View File

@@ -64,13 +64,13 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator
'entry.type' => $item->getType(),
'entry.category' => $item->getCategory(),
'entry.rate' => $formatter->getFormattedMoney($appliedRate, $currency),
'entry.rate_nc' => $formatter->getFormattedMoney($appliedRate, null),
'entry.rate_nc' => $formatter->getFormattedMoney($appliedRate, $currency, false),
'entry.rate_plain' => $appliedRate,
'entry.rate_internal' => $formatter->getFormattedMoney($internalRate, $currency),
'entry.rate_internal_nc' => $formatter->getFormattedMoney($internalRate, null),
'entry.rate_internal_nc' => $formatter->getFormattedMoney($internalRate, $currency, false),
'entry.rate_internal_plain' => $internalRate,
'entry.total' => $formatter->getFormattedMoney($rate, $currency),
'entry.total_nc' => $formatter->getFormattedMoney($rate, null),
'entry.total_nc' => $formatter->getFormattedMoney($rate, $currency, false),
'entry.total_plain' => $rate,
'entry.currency' => $currency,
'entry.duration' => $item->getDuration(),

View File

@@ -31,15 +31,15 @@ class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
'invoice.currency_symbol' => $formatter->getCurrencySymbol($currency),
'invoice.vat' => $model->getCalculator()->getVat(),
'invoice.tax' => $formatter->getFormattedMoney($tax, $currency),
'invoice.tax_nc' => $formatter->getFormattedMoney($tax, null),
'invoice.tax_nc' => $formatter->getFormattedMoney($tax, $currency, false),
'invoice.tax_plain' => $tax,
'invoice.total_time' => $formatter->getFormattedDuration($model->getCalculator()->getTimeWorked()),
'invoice.duration_decimal' => $formatter->getFormattedDecimalDuration($model->getCalculator()->getTimeWorked()),
'invoice.total' => $formatter->getFormattedMoney($total, $currency),
'invoice.total_nc' => $formatter->getFormattedMoney($total, null),
'invoice.total_nc' => $formatter->getFormattedMoney($total, $currency, false),
'invoice.total_plain' => $total,
'invoice.subtotal' => $formatter->getFormattedMoney($subtotal, $currency),
'invoice.subtotal_nc' => $formatter->getFormattedMoney($subtotal, null),
'invoice.subtotal_nc' => $formatter->getFormattedMoney($subtotal, $currency, false),
'invoice.subtotal_plain' => $subtotal,
'template.name' => $model->getTemplate()->getName(),

View File

@@ -53,7 +53,7 @@ class InvoiceModelProjectHydrator implements InvoiceModelHydrator
$prefix . 'end_date' => null !== $project->getEnd() ? $formatter->getFormattedDateTime($project->getEnd()) : '',
$prefix . 'order_date' => null !== $project->getOrderDate() ? $formatter->getFormattedDateTime($project->getOrderDate()) : '',
$prefix . 'budget_money' => $formatter->getFormattedMoney($project->getBudget(), $currency),
$prefix . 'budget_money_nc' => $formatter->getFormattedMoney($project->getBudget(), null),
$prefix . 'budget_money_nc' => $formatter->getFormattedMoney($project->getBudget(), $currency, false),
$prefix . 'budget_money_plain' => $project->getBudget(),
$prefix . 'budget_time' => $project->getTimeBudget(),
$prefix . 'budget_time_decimal' => $formatter->getFormattedDecimalDuration($project->getTimeBudget()),

View File

@@ -29,9 +29,10 @@ interface InvoiceFormatter
/**
* @param int|float $amount
* @param string|null $currency
* @return mixed
* @param bool $withCurrency
* @return string
*/
public function getFormattedMoney($amount, $currency);
public function getFormattedMoney($amount, ?string $currency, bool $withCurrency = true);
/**
* @param \DateTime $date

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Invoice\Renderer;
use App\Entity\InvoiceDocument;
use App\Invoice\InvoiceModel;
use App\Invoice\RendererInterface;
use App\Twig\DateExtensions;
use App\Twig\LocaleExtensions;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Twig\Environment;
/**
* @internal
*/
abstract class AbstractTwigRenderer implements RendererInterface
{
/**
* @var Environment
*/
private $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
protected function renderTwigTemplate(InvoiceDocument $document, InvoiceModel $model): string
{
$previousLocale = $this->changeTwigLocale($this->twig, $model->getTemplate()->getLanguage());
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
'model' => $model
]);
$this->changeTwigLocale($this->twig, $previousLocale);
return $content;
}
private function changeTwigLocale(Environment $twig, ?string $locale = null): ?string
{
// for invoices that don't have a language configured (using request locale)
if (null === $locale) {
return null;
}
/** @var TranslationExtension $extension */
$extension = $twig->getExtension(TranslationExtension::class);
/** @var LocaleAwareInterface $translator */
$translator = $extension->getTranslator();
$previousLocale = $translator->getLocale();
$translator->setLocale($locale);
/** @var LocaleExtensions $extension */
$extension = $twig->getExtension(LocaleExtensions::class);
$extension->setLocale($locale);
/** @var DateExtensions $extension */
$extension = $twig->getExtension(DateExtensions::class);
$extension->setLocale($locale);
return $previousLocale;
}
}

View File

@@ -12,23 +12,11 @@ namespace App\Invoice\Renderer;
use App\Entity\InvoiceDocument;
use App\Invoice\InvoiceFilename;
use App\Invoice\InvoiceModel;
use App\Invoice\RendererInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Twig\Environment;
final class JsonRenderer implements RendererInterface
final class JsonRenderer extends AbstractTwigRenderer
{
/**
* @var Environment
*/
private $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
public function supports(InvoiceDocument $document): bool
{
return stripos($document->getFilename(), '.json.twig') !== false;
@@ -36,9 +24,7 @@ final class JsonRenderer implements RendererInterface
public function render(InvoiceDocument $document, InvoiceModel $model): Response
{
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
'model' => $model
]);
$content = $this->renderTwigTemplate($document, $model);
$filename = (string) new InvoiceFilename($model);
$response = new Response($content);

View File

@@ -12,18 +12,13 @@ namespace App\Invoice\Renderer;
use App\Entity\InvoiceDocument;
use App\Invoice\InvoiceFilename;
use App\Invoice\InvoiceModel;
use App\Invoice\RendererInterface;
use App\Utils\HtmlToPdfConverter;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Twig\Environment;
final class PdfRenderer implements RendererInterface
final class PdfRenderer extends AbstractTwigRenderer
{
/**
* @var Environment
*/
private $twig;
/**
* @var HtmlToPdfConverter
*/
@@ -31,7 +26,7 @@ final class PdfRenderer implements RendererInterface
public function __construct(Environment $twig, HtmlToPdfConverter $converter)
{
$this->twig = $twig;
parent::__construct($twig);
$this->converter = $converter;
}
@@ -42,9 +37,7 @@ final class PdfRenderer implements RendererInterface
public function render(InvoiceDocument $document, InvoiceModel $model): Response
{
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
'model' => $model
]);
$content = $this->renderTwigTemplate($document, $model);
$content = $this->converter->convertToPdf($content, [
'setAutoTopMargin' => 'pad',

View File

@@ -12,23 +12,11 @@ namespace App\Invoice\Renderer;
use App\Entity\InvoiceDocument;
use App\Invoice\InvoiceFilename;
use App\Invoice\InvoiceModel;
use App\Invoice\RendererInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Twig\Environment;
final class TextRenderer implements RendererInterface
final class TextRenderer extends AbstractTwigRenderer
{
/**
* @var Environment
*/
private $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
public function supports(InvoiceDocument $document): bool
{
return stripos($document->getFilename(), '.txt.twig') !== false;
@@ -36,9 +24,7 @@ final class TextRenderer implements RendererInterface
public function render(InvoiceDocument $document, InvoiceModel $model): Response
{
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
'model' => $model
]);
$content = $this->renderTwigTemplate($document, $model);
$filename = (string) new InvoiceFilename($model);
$response = new Response($content);

View File

@@ -11,22 +11,10 @@ namespace App\Invoice\Renderer;
use App\Entity\InvoiceDocument;
use App\Invoice\InvoiceModel;
use App\Invoice\RendererInterface;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;
final class TwigRenderer implements RendererInterface
final class TwigRenderer extends AbstractTwigRenderer
{
/**
* @var Environment
*/
private $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
public function supports(InvoiceDocument $document): bool
{
return stripos($document->getFilename(), '.html.twig') !== false;
@@ -34,9 +22,7 @@ final class TwigRenderer implements RendererInterface
public function render(InvoiceDocument $document, InvoiceModel $model): Response
{
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
'model' => $model
]);
$content = $this->renderTwigTemplate($document, $model);
$response = new Response();
$response->setContent($content);

View File

@@ -12,23 +12,11 @@ namespace App\Invoice\Renderer;
use App\Entity\InvoiceDocument;
use App\Invoice\InvoiceFilename;
use App\Invoice\InvoiceModel;
use App\Invoice\RendererInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Twig\Environment;
final class XmlRenderer implements RendererInterface
final class XmlRenderer extends AbstractTwigRenderer
{
/**
* @var Environment
*/
private $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
public function supports(InvoiceDocument $document): bool
{
return stripos($document->getFilename(), '.xml.twig') !== false;
@@ -36,9 +24,7 @@ final class XmlRenderer implements RendererInterface
public function render(InvoiceDocument $document, InvoiceModel $model): Response
{
$content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
'model' => $model
]);
$content = $this->renderTwigTemplate($document, $model);
$filename = (string) new InvoiceFilename($model);
$response = new Response($content);