added decimal format variable for invoice duration (#1037)
This commit is contained in:
@@ -89,4 +89,13 @@ abstract class AbstractRenderer
|
|||||||
{
|
{
|
||||||
return $this->extension->duration($seconds);
|
return $this->extension->duration($seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $seconds
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function getFormattedDecimalDuration($seconds)
|
||||||
|
{
|
||||||
|
return $this->extension->durationDecimal($seconds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ trait RendererTrait
|
|||||||
*/
|
*/
|
||||||
abstract protected function getFormattedDuration($seconds);
|
abstract protected function getFormattedDuration($seconds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $seconds
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
abstract protected function getFormattedDecimalDuration($seconds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param InvoiceModel $model
|
* @param InvoiceModel $model
|
||||||
* @return array
|
* @return array
|
||||||
@@ -93,6 +99,7 @@ trait RendererTrait
|
|||||||
'invoice.vat' => $model->getCalculator()->getVat(),
|
'invoice.vat' => $model->getCalculator()->getVat(),
|
||||||
'invoice.tax' => $this->getFormattedMoney($model->getCalculator()->getTax(), $currency),
|
'invoice.tax' => $this->getFormattedMoney($model->getCalculator()->getTax(), $currency),
|
||||||
'invoice.total_time' => $this->getFormattedDuration($model->getCalculator()->getTimeWorked()),
|
'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.total' => $this->getFormattedMoney($model->getCalculator()->getTotal(), $currency),
|
||||||
'invoice.subtotal' => $this->getFormattedMoney($model->getCalculator()->getSubtotal(), $currency),
|
'invoice.subtotal' => $this->getFormattedMoney($model->getCalculator()->getSubtotal(), $currency),
|
||||||
|
|
||||||
@@ -177,7 +184,7 @@ trait RendererTrait
|
|||||||
if (null !== $timesheet->getFixedRate()) {
|
if (null !== $timesheet->getFixedRate()) {
|
||||||
$rate = $timesheet->getFixedRate();
|
$rate = $timesheet->getFixedRate();
|
||||||
$hourlyRate = $timesheet->getFixedRate();
|
$hourlyRate = $timesheet->getFixedRate();
|
||||||
$amount = 1;
|
$amount = 1; // FIXME fixed rates
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($description)) {
|
if (empty($description)) {
|
||||||
@@ -206,6 +213,7 @@ trait RendererTrait
|
|||||||
'entry.total' => $this->getFormattedMoney($rate, $currency),
|
'entry.total' => $this->getFormattedMoney($rate, $currency),
|
||||||
'entry.currency' => $currency,
|
'entry.currency' => $currency,
|
||||||
'entry.duration' => $timesheet->getDuration(),
|
'entry.duration' => $timesheet->getDuration(),
|
||||||
|
'entry.duration_decimal' => $this->getFormattedDecimalDuration($timesheet->getDuration()),
|
||||||
'entry.duration_minutes' => number_format($timesheet->getDuration() / 60),
|
'entry.duration_minutes' => number_format($timesheet->getDuration() / 60),
|
||||||
'entry.begin' => $this->getFormattedDateTime($begin),
|
'entry.begin' => $this->getFormattedDateTime($begin),
|
||||||
'entry.begin_time' => $this->getFormattedTime($begin),
|
'entry.begin_time' => $this->getFormattedTime($begin),
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class Extensions extends AbstractExtension
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
new TwigFilter('duration', [$this, 'duration']),
|
new TwigFilter('duration', [$this, 'duration']),
|
||||||
|
new TwigFilter('duration_decimal', [$this, 'durationDecimal']),
|
||||||
new TwigFilter('money', [$this, 'money']),
|
new TwigFilter('money', [$this, 'money']),
|
||||||
new TwigFilter('currency', [$this, 'currency']),
|
new TwigFilter('currency', [$this, 'currency']),
|
||||||
new TwigFilter('country', [$this, 'country']),
|
new TwigFilter('country', [$this, 'country']),
|
||||||
@@ -102,6 +103,30 @@ class Extensions extends AbstractExtension
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function duration($duration, $format = null)
|
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) {
|
if (null === $duration) {
|
||||||
$duration = 0;
|
$duration = 0;
|
||||||
@@ -116,19 +141,15 @@ class Extensions extends AbstractExtension
|
|||||||
$duration = $seconds;
|
$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) {
|
if ($seconds < 0) {
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $format) {
|
|
||||||
$format = $this->localeSettings->getDurationFormat();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->durationFormatter->format($seconds, $format);
|
return $this->durationFormatter->format($seconds, $format);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +180,33 @@ class Extensions extends AbstractExtension
|
|||||||
return Constants::HOMEPAGE . '/documentation/' . $url;
|
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 float $amount
|
||||||
* @param string $currency
|
* @param string $currency
|
||||||
@@ -166,19 +214,11 @@ class Extensions extends AbstractExtension
|
|||||||
*/
|
*/
|
||||||
public function money($amount, $currency = null)
|
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) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
{% set duration = entry.duration|duration %}
|
{% set duration = entry.duration|duration %}
|
||||||
{% if entry.fixedRate is not null %}
|
{% if entry.fixedRate is not null %}
|
||||||
{% set rate = entry.fixedRate %}
|
{% set rate = entry.fixedRate %}
|
||||||
{% set duration = 1 %}
|
{% set duration = 1 %}{# FIXME fixed rates #}
|
||||||
{% elseif entry.hourlyRate is not null %}
|
{% elseif entry.hourlyRate is not null %}
|
||||||
{% set rate = entry.hourlyRate %}
|
{% set rate = entry.hourlyRate %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
{% set duration = entry.duration|duration %}
|
{% set duration = entry.duration|duration %}
|
||||||
{% if entry.fixedRate is not null %}
|
{% if entry.fixedRate is not null %}
|
||||||
{% set rate = entry.fixedRate %}
|
{% set rate = entry.fixedRate %}
|
||||||
{% set duration = 1 %}
|
{% set duration = 1 %}{# FIXME fixed rates #}
|
||||||
{% elseif entry.hourlyRate is not null %}
|
{% elseif entry.hourlyRate is not null %}
|
||||||
{% set rate = entry.hourlyRate %}
|
{% set rate = entry.hourlyRate %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
{% set duration = entry.duration|duration %}
|
{% set duration = entry.duration|duration %}
|
||||||
{% if entry.fixedRate is not null %}
|
{% if entry.fixedRate is not null %}
|
||||||
{% set rate = entry.fixedRate %}
|
{% set rate = entry.fixedRate %}
|
||||||
{% set duration = 1 %}
|
{% set duration = 1 %}{# FIXME fixed rates #}
|
||||||
{% elseif entry.hourlyRate is not null %}
|
{% elseif entry.hourlyRate is not null %}
|
||||||
{% set rate = entry.hourlyRate %}
|
{% set rate = entry.hourlyRate %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -86,6 +86,9 @@
|
|||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
{% if model.query.user is empty %}
|
||||||
|
<th></th>
|
||||||
|
{% endif %}
|
||||||
<th>{{ 'invoice.total_working_time'|trans }}</th>
|
<th>{{ 'invoice.total_working_time'|trans }}</th>
|
||||||
<th>{{ model.calculator.timeWorked|duration }}</th>
|
<th>{{ model.calculator.timeWorked|duration }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -80,6 +80,15 @@ class DebugRenderer implements RendererInterface
|
|||||||
return $seconds;
|
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.
|
* Render the given InvoiceDocument with the data from the InvoiceModel into a stupid array for testing only.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class DebugRendererTest extends TestCase
|
|||||||
'invoice.vat',
|
'invoice.vat',
|
||||||
'invoice.tax',
|
'invoice.tax',
|
||||||
'invoice.total_time',
|
'invoice.total_time',
|
||||||
|
'invoice.duration_decimal',
|
||||||
'invoice.total',
|
'invoice.total',
|
||||||
'invoice.subtotal',
|
'invoice.subtotal',
|
||||||
'template.name',
|
'template.name',
|
||||||
@@ -126,6 +127,7 @@ class DebugRendererTest extends TestCase
|
|||||||
'entry.total',
|
'entry.total',
|
||||||
'entry.currency',
|
'entry.currency',
|
||||||
'entry.duration',
|
'entry.duration',
|
||||||
|
'entry.duration_decimal',
|
||||||
'entry.duration_minutes',
|
'entry.duration_minutes',
|
||||||
'entry.begin',
|
'entry.begin',
|
||||||
'entry.begin_time',
|
'entry.begin_time',
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class ExtensionsTest extends TestCase
|
|||||||
|
|
||||||
public function testGetFilters()
|
public function testGetFilters()
|
||||||
{
|
{
|
||||||
$filters = ['duration', 'money', 'currency', 'country', 'docu_link'];
|
$filters = ['duration', 'duration_decimal', 'money', 'currency', 'country', 'docu_link'];
|
||||||
$sut = $this->getSut($this->localeDe);
|
$sut = $this->getSut($this->localeDe);
|
||||||
$twigFilters = $sut->getFilters();
|
$twigFilters = $sut->getFilters();
|
||||||
$this->assertCount(count($filters), $twigFilters);
|
$this->assertCount(count($filters), $twigFilters);
|
||||||
@@ -200,6 +200,33 @@ class ExtensionsTest extends TestCase
|
|||||||
$this->assertEquals('00:00 h', $sut->duration(null));
|
$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)
|
protected function getTimesheet($seconds)
|
||||||
{
|
{
|
||||||
$begin = new \DateTime();
|
$begin = new \DateTime();
|
||||||
|
|||||||
Reference in New Issue
Block a user