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
{
use BudgetHydratorTrait;
private $activityStatistic;
public function __construct(ActivityStatisticService $activityStatistic)
@@ -59,21 +61,8 @@ class InvoiceModelActivityHydrator implements InvoiceModelHydrator
];
$statistic = $this->activityStatistic->getBudgetStatisticModel($activity, $model->getQuery()->getEnd());
$formatter = $model->getFormatter();
$currency = $model->getCurrency();
if ($model->getTemplate()->isDecimalDuration()) {
$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(),
]);
$values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model));
foreach ($activity->getVisibleMetaFields() as $metaField) {
$values = array_merge($values, [

View File

@@ -15,6 +15,8 @@ use App\Invoice\InvoiceModelHydrator;
class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
{
use BudgetHydratorTrait;
private $customerStatistic;
public function __construct(CustomerStatisticService $customerStatistic)
@@ -48,21 +50,8 @@ class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
];
$statistic = $this->customerStatistic->getBudgetStatisticModel($customer, $model->getQuery()->getEnd());
$currency = $model->getCurrency();
$formatter = $model->getFormatter();
if ($model->getTemplate()->isDecimalDuration()) {
$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(),
]);
$values = array_merge($values, $this->getBudgetValues('customer.', $statistic, $model));
foreach ($customer->getMetaFields() as $metaField) {
$values = array_merge($values, [

View File

@@ -16,6 +16,8 @@ use App\Project\ProjectStatisticService;
class InvoiceModelProjectHydrator implements InvoiceModelHydrator
{
use BudgetHydratorTrait;
private $projectStatistic;
public function __construct(ProjectStatisticService $projectStatistic)
@@ -73,18 +75,7 @@ class InvoiceModelProjectHydrator implements InvoiceModelHydrator
$statistic = $this->projectStatistic->getBudgetStatisticModel($project, $model->getQuery()->getEnd());
if ($model->getTemplate()->isDecimalDuration()) {
$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(),
]);
$values = array_merge($values, $this->getBudgetValues($prefix, $statistic, $model));
foreach ($project->getVisibleMetaFields() as $metaField) {
$values = array_merge($values, [

View File

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

View File

@@ -80,13 +80,23 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
public function getDurationBillable(): int
{
if ($this->isMonthlyBudget()) {
if ($this->statistic === null) {
return 0;
}
return $this->statistic->getDurationBillable();
return $this->getDurationBillableRelative();
}
return $this->getDurationBillableTotal();
}
public function getDurationBillableRelative(): int
{
if ($this->statistic === null) {
return 0;
}
return $this->statistic->getDurationBillable();
}
public function getDurationBillableTotal(): int
{
if ($this->statisticTotal === null) {
return 0;
}
@@ -96,7 +106,14 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
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;
}
@@ -118,7 +135,14 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
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;
}
@@ -131,13 +155,23 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
public function getRateBillable(): float
{
if ($this->isMonthlyBudget()) {
if ($this->statistic === null) {
return 0.00;
}
return $this->statistic->getRateBillable();
return $this->getRateBillableRelative();
}
return $this->getRateBillableTotal();
}
public function getRateBillableRelative(): float
{
if ($this->statistic === null) {
return 0.00;
}
return $this->statistic->getRateBillable();
}
public function getRateBillableTotal(): float
{
if ($this->statisticTotal === null) {
return 0.00;
}

View File

@@ -71,8 +71,12 @@ class BudgetStatisticModelTest extends TestCase
self::assertSame($entity, $sut->getEntity());
self::assertSame(23, $sut->getDurationBillable());
self::assertSame(23, $sut->getDurationBillableRelative());
self::assertSame(223, $sut->getDurationBillableTotal());
self::assertSame(53, $sut->getDuration());
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(147.95, $sut->getInternalRate());