fix invoice budget calculation (#3024)
This commit is contained in:
38
src/Invoice/Hydrator/BudgetHydratorTrait.php
Normal file
38
src/Invoice/Hydrator/BudgetHydratorTrait.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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, [
|
||||
|
||||
@@ -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, [
|
||||
|
||||
@@ -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, [
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user