fix invoice budget calculation (#3024)

This commit is contained in:
Kevin Papst
2021-12-15 17:56:18 +01:00
committed by GitHub
parent 358959522d
commit 3621b8c27c
7 changed files with 100 additions and 52 deletions

View File

@@ -0,0 +1,38 @@
<?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\Hydrator;
use App\Invoice\InvoiceModel;
use App\Model\BudgetStatisticModel;
trait BudgetHydratorTrait
{
protected function getBudgetValues(string $prefix, BudgetStatisticModel $statistic, InvoiceModel $model): array
{
$formatter = $model->getFormatter();
$currency = $model->getCurrency();
$budgetOpen = $statistic->getBudgetOpenRelative();
$budgetTimeOpen = $statistic->getTimeBudgetOpenRelative();
if ($model->getTemplate()->isDecimalDuration()) {
$budgetOpenDuration = $formatter->getFormattedDecimalDuration($budgetTimeOpen);
} else {
$budgetOpenDuration = $formatter->getFormattedDuration($budgetTimeOpen);
}
return [
$prefix . 'budget_open' => $formatter->getFormattedMoney($budgetOpen, $currency),
$prefix . 'budget_open_plain' => $budgetOpen,
$prefix . 'time_budget_open' => $budgetOpenDuration,
$prefix . 'time_budget_open_plain' => $budgetTimeOpen,
];
}
}

View File

@@ -16,6 +16,8 @@ use App\Invoice\InvoiceModelHydrator;
class InvoiceModelActivityHydrator implements InvoiceModelHydrator class InvoiceModelActivityHydrator implements InvoiceModelHydrator
{ {
use BudgetHydratorTrait;
private $activityStatistic; private $activityStatistic;
public function __construct(ActivityStatisticService $activityStatistic) public function __construct(ActivityStatisticService $activityStatistic)
@@ -59,21 +61,8 @@ class InvoiceModelActivityHydrator implements InvoiceModelHydrator
]; ];
$statistic = $this->activityStatistic->getBudgetStatisticModel($activity, $model->getQuery()->getEnd()); $statistic = $this->activityStatistic->getBudgetStatisticModel($activity, $model->getQuery()->getEnd());
$formatter = $model->getFormatter();
$currency = $model->getCurrency();
if ($model->getTemplate()->isDecimalDuration()) { $values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model));
$budgetOpenDuration = $formatter->getFormattedDecimalDuration($statistic->getTimeBudgetOpen());
} else {
$budgetOpenDuration = $formatter->getFormattedDuration($statistic->getTimeBudgetOpen());
}
$values = array_merge($values, [
$prefix . 'budget_open' => $formatter->getFormattedMoney($statistic->getBudgetOpen(), $currency),
$prefix . 'budget_open_plain' => $statistic->getBudgetOpen(),
$prefix . 'time_budget_open' => $budgetOpenDuration,
$prefix . 'time_budget_open_plain' => $statistic->getTimeBudgetOpen(),
]);
foreach ($activity->getVisibleMetaFields() as $metaField) { foreach ($activity->getVisibleMetaFields() as $metaField) {
$values = array_merge($values, [ $values = array_merge($values, [

View File

@@ -15,6 +15,8 @@ use App\Invoice\InvoiceModelHydrator;
class InvoiceModelCustomerHydrator implements InvoiceModelHydrator class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
{ {
use BudgetHydratorTrait;
private $customerStatistic; private $customerStatistic;
public function __construct(CustomerStatisticService $customerStatistic) public function __construct(CustomerStatisticService $customerStatistic)
@@ -48,21 +50,8 @@ class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
]; ];
$statistic = $this->customerStatistic->getBudgetStatisticModel($customer, $model->getQuery()->getEnd()); $statistic = $this->customerStatistic->getBudgetStatisticModel($customer, $model->getQuery()->getEnd());
$currency = $model->getCurrency();
$formatter = $model->getFormatter();
if ($model->getTemplate()->isDecimalDuration()) { $values = array_merge($values, $this->getBudgetValues('customer.', $statistic, $model));
$budgetOpenDuration = $formatter->getFormattedDecimalDuration($statistic->getTimeBudgetOpen());
} else {
$budgetOpenDuration = $formatter->getFormattedDuration($statistic->getTimeBudgetOpen());
}
$values = array_merge($values, [
'customer.budget_open' => $formatter->getFormattedMoney($statistic->getBudgetOpen(), $currency),
'customer.budget_open_plain' => $statistic->getBudgetOpen(),
'customer.time_budget_open' => $budgetOpenDuration,
'customer.time_budget_open_plain' => $statistic->getTimeBudgetOpen(),
]);
foreach ($customer->getMetaFields() as $metaField) { foreach ($customer->getMetaFields() as $metaField) {
$values = array_merge($values, [ $values = array_merge($values, [

View File

@@ -16,6 +16,8 @@ use App\Project\ProjectStatisticService;
class InvoiceModelProjectHydrator implements InvoiceModelHydrator class InvoiceModelProjectHydrator implements InvoiceModelHydrator
{ {
use BudgetHydratorTrait;
private $projectStatistic; private $projectStatistic;
public function __construct(ProjectStatisticService $projectStatistic) public function __construct(ProjectStatisticService $projectStatistic)
@@ -73,18 +75,7 @@ class InvoiceModelProjectHydrator implements InvoiceModelHydrator
$statistic = $this->projectStatistic->getBudgetStatisticModel($project, $model->getQuery()->getEnd()); $statistic = $this->projectStatistic->getBudgetStatisticModel($project, $model->getQuery()->getEnd());
if ($model->getTemplate()->isDecimalDuration()) { $values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model));
$budgetOpenDuration = $formatter->getFormattedDecimalDuration($statistic->getTimeBudgetOpen());
} else {
$budgetOpenDuration = $formatter->getFormattedDuration($statistic->getTimeBudgetOpen());
}
$values = array_merge($values, [
$prefix . 'budget_open' => $formatter->getFormattedMoney($statistic->getBudgetOpen(), $currency),
$prefix . 'budget_open_plain' => $statistic->getBudgetOpen(),
$prefix . 'time_budget_open' => $budgetOpenDuration,
$prefix . 'time_budget_open_plain' => $statistic->getTimeBudgetOpen(),
]);
foreach ($project->getVisibleMetaFields() as $metaField) { foreach ($project->getVisibleMetaFields() as $metaField) {
$values = array_merge($values, [ $values = array_merge($values, [

View File

@@ -78,6 +78,9 @@ final class InvoiceModel
*/ */
private $invoiceNumber; private $invoiceNumber;
/**
* @internal use InvoiceModelFactory
*/
public function __construct(InvoiceFormatter $formatter, CustomerStatisticService $customerStatistic, ProjectStatisticService $projectStatistic, ActivityStatisticService $activityStatistic) public function __construct(InvoiceFormatter $formatter, CustomerStatisticService $customerStatistic, ProjectStatisticService $projectStatistic, ActivityStatisticService $activityStatistic)
{ {
$this->invoiceDate = new \DateTime(); $this->invoiceDate = new \DateTime();

View File

@@ -80,6 +80,14 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
public function getDurationBillable(): int public function getDurationBillable(): int
{ {
if ($this->isMonthlyBudget()) { if ($this->isMonthlyBudget()) {
return $this->getDurationBillableRelative();
}
return $this->getDurationBillableTotal();
}
public function getDurationBillableRelative(): int
{
if ($this->statistic === null) { if ($this->statistic === null) {
return 0; return 0;
} }
@@ -87,6 +95,8 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
return $this->statistic->getDurationBillable(); return $this->statistic->getDurationBillable();
} }
public function getDurationBillableTotal(): int
{
if ($this->statisticTotal === null) { if ($this->statisticTotal === null) {
return 0; return 0;
} }
@@ -96,7 +106,14 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
public function getTimeBudgetOpen(): int public function getTimeBudgetOpen(): int
{ {
$value = $this->getTimeBudget() - $this->getTimeBudgetSpent(); $value = $this->getTimeBudget() - $this->getDurationBillable();
return $value > 0 ? $value : 0;
}
public function getTimeBudgetOpenRelative(): int
{
$value = $this->getTimeBudget() - $this->getDurationBillableRelative();
return $value > 0 ? $value : 0; return $value > 0 ? $value : 0;
} }
@@ -118,7 +135,14 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
public function getBudgetOpen(): float public function getBudgetOpen(): float
{ {
$value = $this->getBudget() - $this->getBudgetSpent(); $value = $this->getBudget() - $this->getRateBillable();
return $value > 0 ? $value : 0;
}
public function getBudgetOpenRelative(): float
{
$value = $this->getBudget() - $this->getRateBillableRelative();
return $value > 0 ? $value : 0; return $value > 0 ? $value : 0;
} }
@@ -131,6 +155,14 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
public function getRateBillable(): float public function getRateBillable(): float
{ {
if ($this->isMonthlyBudget()) { if ($this->isMonthlyBudget()) {
return $this->getRateBillableRelative();
}
return $this->getRateBillableTotal();
}
public function getRateBillableRelative(): float
{
if ($this->statistic === null) { if ($this->statistic === null) {
return 0.00; return 0.00;
} }
@@ -138,6 +170,8 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
return $this->statistic->getRateBillable(); return $this->statistic->getRateBillable();
} }
public function getRateBillableTotal(): float
{
if ($this->statisticTotal === null) { if ($this->statisticTotal === null) {
return 0.00; return 0.00;
} }

View File

@@ -71,8 +71,12 @@ class BudgetStatisticModelTest extends TestCase
self::assertSame($entity, $sut->getEntity()); self::assertSame($entity, $sut->getEntity());
self::assertSame(23, $sut->getDurationBillable()); self::assertSame(23, $sut->getDurationBillable());
self::assertSame(23, $sut->getDurationBillableRelative());
self::assertSame(223, $sut->getDurationBillableTotal());
self::assertSame(53, $sut->getDuration()); self::assertSame(53, $sut->getDuration());
self::assertSame(13.00, $sut->getRateBillable()); self::assertSame(13.00, $sut->getRateBillable());
self::assertSame(13.00, $sut->getRateBillableRelative());
self::assertSame(213.00, $sut->getRateBillableTotal());
self::assertSame(47.00, $sut->getRate()); self::assertSame(47.00, $sut->getRate());
self::assertSame(147.95, $sut->getInternalRate()); self::assertSame(147.95, $sut->getInternalRate());