diff --git a/src/Invoice/Renderer/AbstractRenderer.php b/src/Invoice/Renderer/AbstractRenderer.php index a41e5753..412d1867 100644 --- a/src/Invoice/Renderer/AbstractRenderer.php +++ b/src/Invoice/Renderer/AbstractRenderer.php @@ -89,4 +89,13 @@ abstract class AbstractRenderer { return $this->extension->duration($seconds); } + + /** + * @param int $seconds + * @return mixed + */ + protected function getFormattedDecimalDuration($seconds) + { + return $this->extension->durationDecimal($seconds); + } } diff --git a/src/Invoice/Renderer/RendererTrait.php b/src/Invoice/Renderer/RendererTrait.php index 443439b2..f4872c8c 100644 --- a/src/Invoice/Renderer/RendererTrait.php +++ b/src/Invoice/Renderer/RendererTrait.php @@ -74,6 +74,12 @@ trait RendererTrait */ abstract protected function getFormattedDuration($seconds); + /** + * @param int $seconds + * @return mixed + */ + abstract protected function getFormattedDecimalDuration($seconds); + /** * @param InvoiceModel $model * @return array @@ -93,6 +99,7 @@ trait RendererTrait 'invoice.vat' => $model->getCalculator()->getVat(), 'invoice.tax' => $this->getFormattedMoney($model->getCalculator()->getTax(), $currency), 'invoice.total_time' => $this->getFormattedDuration($model->getCalculator()->getTimeWorked()), + 'invoice.duration_decimal' => $this->getFormattedDecimalDuration($model->getCalculator()->getTimeWorked()), 'invoice.total' => $this->getFormattedMoney($model->getCalculator()->getTotal(), $currency), 'invoice.subtotal' => $this->getFormattedMoney($model->getCalculator()->getSubtotal(), $currency), @@ -177,7 +184,7 @@ trait RendererTrait if (null !== $timesheet->getFixedRate()) { $rate = $timesheet->getFixedRate(); $hourlyRate = $timesheet->getFixedRate(); - $amount = 1; + $amount = 1; // FIXME fixed rates } if (empty($description)) { @@ -206,6 +213,7 @@ trait RendererTrait 'entry.total' => $this->getFormattedMoney($rate, $currency), 'entry.currency' => $currency, 'entry.duration' => $timesheet->getDuration(), + 'entry.duration_decimal' => $this->getFormattedDecimalDuration($timesheet->getDuration()), 'entry.duration_minutes' => number_format($timesheet->getDuration() / 60), 'entry.begin' => $this->getFormattedDateTime($begin), 'entry.begin_time' => $this->getFormattedTime($begin), diff --git a/src/Twig/Extensions.php b/src/Twig/Extensions.php index e33f7d92..b69ac408 100644 --- a/src/Twig/Extensions.php +++ b/src/Twig/Extensions.php @@ -63,6 +63,7 @@ class Extensions extends AbstractExtension { return [ new TwigFilter('duration', [$this, 'duration']), + new TwigFilter('duration_decimal', [$this, 'durationDecimal']), new TwigFilter('money', [$this, 'money']), new TwigFilter('currency', [$this, 'currency']), new TwigFilter('country', [$this, 'country']), @@ -102,6 +103,30 @@ class Extensions extends AbstractExtension * @return string */ public function duration($duration, $format = null) + { + $duration = $this->getSecondsForDuration($duration); + + if (null === $format) { + $format = $this->localeSettings->getDurationFormat(); + } + + return $this->formatDuration($duration, $format); + } + + /** + * Transforms seconds into a decimal formatted duration string. + * + * @param int|Timesheet $duration + * @return string + */ + public function durationDecimal($duration) + { + $duration = $this->getSecondsForDuration($duration); + + return $this->getNumberFormatter()->format(number_format($duration / 3600, 2)); + } + + private function getSecondsForDuration($duration): int { if (null === $duration) { $duration = 0; @@ -116,19 +141,15 @@ class Extensions extends AbstractExtension $duration = $seconds; } - return $this->formatDuration((int) $duration, $format); + return (int) $duration; } - protected function formatDuration(int $seconds, $format = null): string + protected function formatDuration(int $seconds, string $format): string { if ($seconds < 0) { return '?'; } - if (null === $format) { - $format = $this->localeSettings->getDurationFormat(); - } - return $this->durationFormatter->format($seconds, $format); } @@ -159,6 +180,33 @@ class Extensions extends AbstractExtension return Constants::HOMEPAGE . '/documentation/' . $url; } + private function initLocale() + { + $locale = $this->localeSettings->getLocale(); + + if ($this->locale === $locale) { + return; + } + + $this->locale = $locale; + $this->numberFormatter = new NumberFormatter($locale, NumberFormatter::DECIMAL); + $this->moneyFormatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); + } + + private function getNumberFormatter(): NumberFormatter + { + $this->initLocale(); + + return $this->numberFormatter; + } + + private function getMoneyFormatter(): NumberFormatter + { + $this->initLocale(); + + return $this->moneyFormatter; + } + /** * @param float $amount * @param string $currency @@ -166,19 +214,11 @@ class Extensions extends AbstractExtension */ public function money($amount, $currency = null) { - $locale = $this->localeSettings->getLocale(); - - if ($this->locale !== $locale) { - $this->locale = $locale; - $this->numberFormatter = new NumberFormatter($locale, NumberFormatter::DECIMAL); - $this->moneyFormatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); - } - if (null !== $currency) { - return $this->moneyFormatter->formatCurrency($amount, $currency); + return $this->getMoneyFormatter()->formatCurrency($amount, $currency); } - return $this->numberFormatter->format($amount); + return $this->getNumberFormatter()->format($amount); } /** diff --git a/templates/invoice/index.html.twig b/templates/invoice/index.html.twig index b431e2d1..42ea7a79 100644 --- a/templates/invoice/index.html.twig +++ b/templates/invoice/index.html.twig @@ -45,7 +45,7 @@ {% set duration = entry.duration|duration %} {% if entry.fixedRate is not null %} {% set rate = entry.fixedRate %} - {% set duration = 1 %} + {% set duration = 1 %}{# FIXME fixed rates #} {% elseif entry.hourlyRate is not null %} {% set rate = entry.hourlyRate %} {% else %} diff --git a/templates/invoice/renderer/default.html.twig b/templates/invoice/renderer/default.html.twig index 4f9fcb07..a8382bd7 100644 --- a/templates/invoice/renderer/default.html.twig +++ b/templates/invoice/renderer/default.html.twig @@ -67,7 +67,7 @@ {% set duration = entry.duration|duration %} {% if entry.fixedRate is not null %} {% set rate = entry.fixedRate %} - {% set duration = 1 %} + {% set duration = 1 %}{# FIXME fixed rates #} {% elseif entry.hourlyRate is not null %} {% set rate = entry.hourlyRate %} {% else %} diff --git a/templates/invoice/renderer/freelancer.html.twig b/templates/invoice/renderer/freelancer.html.twig index 096c3344..f4f08293 100644 --- a/templates/invoice/renderer/freelancer.html.twig +++ b/templates/invoice/renderer/freelancer.html.twig @@ -80,7 +80,7 @@ {% set duration = entry.duration|duration %} {% if entry.fixedRate is not null %} {% set rate = entry.fixedRate %} - {% set duration = 1 %} + {% set duration = 1 %}{# FIXME fixed rates #} {% elseif entry.hourlyRate is not null %} {% set rate = entry.hourlyRate %} {% else %} diff --git a/templates/invoice/renderer/timesheet.html.twig b/templates/invoice/renderer/timesheet.html.twig index b7497edc..f2364dc9 100644 --- a/templates/invoice/renderer/timesheet.html.twig +++ b/templates/invoice/renderer/timesheet.html.twig @@ -86,6 +86,9 @@ + {% if model.query.user is empty %} + + {% endif %} {{ 'invoice.total_working_time'|trans }} {{ model.calculator.timeWorked|duration }} diff --git a/tests/Invoice/Renderer/DebugRenderer.php b/tests/Invoice/Renderer/DebugRenderer.php index 380273d6..e9c8db7d 100644 --- a/tests/Invoice/Renderer/DebugRenderer.php +++ b/tests/Invoice/Renderer/DebugRenderer.php @@ -80,6 +80,15 @@ class DebugRenderer implements RendererInterface return $seconds; } + /** + * @param mixed $seconds + * @return mixed + */ + protected function getFormattedDecimalDuration($seconds) + { + return $seconds; + } + /** * Render the given InvoiceDocument with the data from the InvoiceModel into a stupid array for testing only. * diff --git a/tests/Invoice/Renderer/DebugRendererTest.php b/tests/Invoice/Renderer/DebugRendererTest.php index d1790bb3..3d0aa4dc 100644 --- a/tests/Invoice/Renderer/DebugRendererTest.php +++ b/tests/Invoice/Renderer/DebugRendererTest.php @@ -61,6 +61,7 @@ class DebugRendererTest extends TestCase 'invoice.vat', 'invoice.tax', 'invoice.total_time', + 'invoice.duration_decimal', 'invoice.total', 'invoice.subtotal', 'template.name', @@ -126,6 +127,7 @@ class DebugRendererTest extends TestCase 'entry.total', 'entry.currency', 'entry.duration', + 'entry.duration_decimal', 'entry.duration_minutes', 'entry.begin', 'entry.begin_time', diff --git a/tests/Twig/ExtensionsTest.php b/tests/Twig/ExtensionsTest.php index 9fd8bb6c..268afecb 100644 --- a/tests/Twig/ExtensionsTest.php +++ b/tests/Twig/ExtensionsTest.php @@ -50,7 +50,7 @@ class ExtensionsTest extends TestCase public function testGetFilters() { - $filters = ['duration', 'money', 'currency', 'country', 'docu_link']; + $filters = ['duration', 'duration_decimal', 'money', 'currency', 'country', 'docu_link']; $sut = $this->getSut($this->localeDe); $twigFilters = $sut->getFilters(); $this->assertCount(count($filters), $twigFilters); @@ -200,6 +200,33 @@ class ExtensionsTest extends TestCase $this->assertEquals('00:00 h', $sut->duration(null)); } + public function testDurationDecimal() + { + $record = $this->getTimesheet(9437); + + $sut = $this->getSut($this->localeEn); + $this->assertEquals('2.62', $sut->durationDecimal($record->getDuration())); + + // test Timesheet object + $this->assertEquals('2.62', $sut->durationDecimal($record)); + + // test extended format + $sut = $this->getSut($this->localeDe, 'de'); + $this->assertEquals('2,62', $sut->durationDecimal($record->getDuration())); + + // test negative duration + $sut = $this->getSut($this->localeEn, 'en'); + $this->assertEquals('0', $sut->durationDecimal('-1')); + + // test zero duration + $sut = $this->getSut($this->localeEn, 'en'); + $this->assertEquals('0', $sut->durationDecimal('0')); + + $sut = $this->getSut($this->localeEn, 'en'); + + $this->assertEquals('0', $sut->durationDecimal(null)); + } + protected function getTimesheet($seconds) { $begin = new \DateTime();