Configurable rate rounding (#5734)
* added invoice hydration of the issuer object * added "rate calculator" mode * new config to select rounding mode * replace static calls with dependency injection
This commit is contained in:
@@ -449,6 +449,11 @@ final class SystemConfigurationController extends AbstractController
|
||||
->setType(TextType::class)
|
||||
->setConstraints([new NotBlank()])
|
||||
->setTranslationDomain('system-configuration'),
|
||||
(new Configuration('invoice.rounding_mode'))
|
||||
->setLabel('invoice.rounding_mode')
|
||||
->setType(ChoiceType::class)
|
||||
->setOptions(['choices' => ['classic' => 'classic', 'decimal' => 'decimal']])
|
||||
->setTranslationDomain('system-configuration'),
|
||||
]),
|
||||
$authentication,
|
||||
(new SystemConfigurationModel('customer'))
|
||||
|
||||
@@ -15,7 +15,8 @@ use App\Entity\Tag;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Entity\UserPreference;
|
||||
use App\Timesheet\Util;
|
||||
use App\Timesheet\RateCalculator\ClassicRateCalculator;
|
||||
use App\Timesheet\RateCalculator\RateCalculatorMode;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
@@ -60,10 +61,14 @@ final class TimesheetFixtures extends Fixture implements FixtureGroupInterface
|
||||
$allUser = $this->getAllUsers($manager);
|
||||
$faker = Factory::create();
|
||||
$all = 0;
|
||||
$calculator = new ClassicRateCalculator();
|
||||
|
||||
foreach ($allUser as $user) {
|
||||
// reload, because the manager might have been cleared
|
||||
$user = $manager->find(User::class, $user->getId());
|
||||
if ($user === null) {
|
||||
continue;
|
||||
}
|
||||
// random amount of timesheet entries for every user
|
||||
$timesheetForUser = rand(self::MIN_TIMESHEETS_PER_USER, self::MAX_TIMESHEETS_PER_USER);
|
||||
|
||||
@@ -84,6 +89,7 @@ final class TimesheetFixtures extends Fixture implements FixtureGroupInterface
|
||||
}
|
||||
|
||||
$entry = $this->createTimesheetEntry(
|
||||
$calculator,
|
||||
$user,
|
||||
$activities[array_rand($activities)],
|
||||
$projects[array_rand($projects)],
|
||||
@@ -98,6 +104,7 @@ final class TimesheetFixtures extends Fixture implements FixtureGroupInterface
|
||||
// create active records
|
||||
if ($all % 3 === 0) {
|
||||
$entry = $this->createTimesheetEntry(
|
||||
$calculator,
|
||||
$user,
|
||||
$activities[array_rand($activities)],
|
||||
$projects[array_rand($projects)],
|
||||
@@ -208,7 +215,7 @@ final class TimesheetFixtures extends Fixture implements FixtureGroupInterface
|
||||
return $this->findRandom($manager, Activity::class, 50);
|
||||
}
|
||||
|
||||
private function createTimesheetEntry(User $user, Activity $activity, Project $project, ?string $description, bool $setEndDate): Timesheet
|
||||
private function createTimesheetEntry(RateCalculatorMode $calculatorMode, User $user, Activity $activity, Project $project, ?string $description, bool $setEndDate): Timesheet
|
||||
{
|
||||
$start = $this->getRandomFirstDay();
|
||||
$start = $start->modify('- ' . (rand(1, 86400)) . ' seconds');
|
||||
@@ -227,7 +234,7 @@ final class TimesheetFixtures extends Fixture implements FixtureGroupInterface
|
||||
|
||||
$duration = $end->getTimestamp() - $start->getTimestamp();
|
||||
$hourlyRate = (float) $user->getPreferenceValue(UserPreference::HOURLY_RATE);
|
||||
$rate = Util::calculateRate($hourlyRate, $duration);
|
||||
$rate = $calculatorMode->calculateRate($hourlyRate, $duration);
|
||||
|
||||
$entry->setEnd($end);
|
||||
$entry->setRate($rate);
|
||||
|
||||
@@ -369,6 +369,10 @@ final class Configuration implements ConfigurationInterface
|
||||
->booleanNode('upload_twig')
|
||||
->defaultFalse()
|
||||
->end()
|
||||
->enumNode('rounding_mode')
|
||||
->values(['decimal', 'classic'])
|
||||
->defaultValue('classic')
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace App\Invoice\Calculator;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\TaxRow;
|
||||
use App\Timesheet\Util;
|
||||
|
||||
abstract class AbstractCalculator
|
||||
{
|
||||
@@ -42,10 +41,10 @@ abstract class AbstractCalculator
|
||||
if (\count($this->cached) === 0) {
|
||||
foreach ($this->calculateEntries() as $entry) {
|
||||
if (!$entry->isFixedRate() && $entry->getHourlyRate() !== null && $entry->getHourlyRate() > 0) {
|
||||
$entry->setDuration(Util::decimalizeDuration($entry->getDuration()));
|
||||
$entry->setDuration($this->model->getRateCalculatorMode()->roundDuration($entry->getDuration()));
|
||||
// when merging many entries, we might run into rounding issues
|
||||
// so we have to recalculate the hourly rate here
|
||||
$entry->setRate(Util::calculateRate($entry->getHourlyRate(), $entry->getDuration()));
|
||||
$entry->setRate($this->model->getRateCalculatorMode()->calculateRate($entry->getHourlyRate(), $entry->getDuration()));
|
||||
}
|
||||
|
||||
$this->cached[] = $entry;
|
||||
|
||||
@@ -32,7 +32,7 @@ final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
|
||||
|
||||
$values = [
|
||||
$prefix . 'id' => $customer->getId(),
|
||||
$prefix . 'address' => $customer->getFormattedAddress() ?? '',
|
||||
$prefix . 'address' => $customer->getFormattedAddress() ?? '', // deprecated since 2.44
|
||||
$prefix . 'address_line1' => $customer->getAddressLine1() ?? '',
|
||||
$prefix . 'address_line2' => $customer->getAddressLine2() ?? '',
|
||||
$prefix . 'address_line3' => $customer->getAddressLine3() ?? '',
|
||||
|
||||
@@ -62,12 +62,12 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
'invoice.subtotal_plain' => $subtotal,
|
||||
|
||||
'template.name' => $template->getName() ?? '',
|
||||
'template.company' => $template->getCompany() ?? '',
|
||||
'template.company' => $template->getCompany() ?? '', // deprecated since 2.45
|
||||
'template.address' => $template->getAddress() ?? '',
|
||||
'template.title' => $template->getTitle() ?? '',
|
||||
'template.payment_terms' => $template->getPaymentTerms() ?? '',
|
||||
'template.due_days' => $template->getDueDays(),
|
||||
'template.vat_id' => $template->getVatId() ?? '',
|
||||
'template.vat_id' => $template->getVatId() ?? '', // deprecated since 2.45
|
||||
'template.contact' => $template->getContact() ?? '',
|
||||
'template.country' => null,
|
||||
'template.country_name' => null,
|
||||
@@ -75,16 +75,16 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
|
||||
'query.begin' => '',
|
||||
'query.begin_day' => '',
|
||||
'query.begin_process' => null, // since 2.14
|
||||
'query.begin_process' => null, // since 2.14
|
||||
'query.begin_month' => '',
|
||||
'query.begin_month_number' => '',
|
||||
'query.begin_year' => '',
|
||||
'query.end' => '', // since 1.9
|
||||
'query.end_day' => '', // since 1.9
|
||||
'query.end_process' => null, // since 2.14
|
||||
'query.end_month' => '', // since 1.9
|
||||
'query.end_month_number' => '', // since 1.9
|
||||
'query.end_year' => '', // since 1.9
|
||||
'query.end' => '', // since 1.9
|
||||
'query.end_day' => '', // since 1.9
|
||||
'query.end_process' => null, // since 2.14
|
||||
'query.end_month' => '', // since 1.9
|
||||
'query.end_month_number' => '', // since 1.9
|
||||
'query.end_year' => '', // since 1.9
|
||||
|
||||
// since 2.0.15
|
||||
'user.see_others' => ($model->getQuery()?->getUser() === null),
|
||||
@@ -105,8 +105,8 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
if ($seller !== null) {
|
||||
$country = $seller->getCountry();
|
||||
if ($country !== null) {
|
||||
$values['template.country'] = $country;
|
||||
$values['template.country_name'] = Countries::getName($country, $language);
|
||||
$values['template.country'] = $country; // deprecated since 2.45
|
||||
$values['template.country_name'] = Countries::getName($country, $language); // deprecated since 2.45
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,14 +116,10 @@ final class InvoiceModelDefaultHydrator implements InvoiceModelHydrator
|
||||
if ($begin !== null) {
|
||||
$values = array_merge($values, [
|
||||
'query.day' => $begin->format('d'),
|
||||
// @deprecated - but impossible to delete
|
||||
'query.month' => $formatter->getFormattedMonthName($begin),
|
||||
// @deprecated - but impossible to delete
|
||||
'query.month_number' => $begin->format('m'),
|
||||
// @deprecated - but impossible to delete
|
||||
'query.year' => $begin->format('Y'),
|
||||
// @deprecated - but impossible to delete
|
||||
'query.begin' => $formatter->getFormattedDateTime($begin),
|
||||
'query.month' => $formatter->getFormattedMonthName($begin), // @deprecated - but impossible to delete
|
||||
'query.month_number' => $begin->format('m'), // @deprecated - but impossible to delete
|
||||
'query.year' => $begin->format('Y'), // @deprecated - but impossible to delete
|
||||
'query.begin' => $formatter->getFormattedDateTime($begin), // @deprecated - but impossible to delete
|
||||
'query.begin_process' => $begin->format(self::DATE_PROCESS_FORMAT), // since 2.14
|
||||
'query.begin_day' => $begin->format('d'),
|
||||
'query.begin_month' => $formatter->getFormattedMonthName($begin),
|
||||
|
||||
65
src/Invoice/Hydrator/InvoiceModelIssuerHydrator.php
Normal file
65
src/Invoice/Hydrator/InvoiceModelIssuerHydrator.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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\Invoice\InvoiceModelHydrator;
|
||||
use Symfony\Component\Intl\Countries;
|
||||
|
||||
final class InvoiceModelIssuerHydrator implements InvoiceModelHydrator
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function hydrate(InvoiceModel $model): array
|
||||
{
|
||||
$customer = $model->getTemplate()->getCustomer();
|
||||
if (null === $customer) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$prefix = 'issuer.';
|
||||
$language = $model->getTemplate()->getLanguage();
|
||||
$country = $customer->getCountry();
|
||||
|
||||
$values = [
|
||||
$prefix . 'id' => $customer->getId(),
|
||||
$prefix . 'address' => $customer->getFormattedAddress() ?? '',
|
||||
$prefix . 'address_line1' => $customer->getAddressLine1() ?? '',
|
||||
$prefix . 'address_line2' => $customer->getAddressLine2() ?? '',
|
||||
$prefix . 'address_line3' => $customer->getAddressLine3() ?? '',
|
||||
$prefix . 'postcode' => $customer->getPostCode() ?? '',
|
||||
$prefix . 'city' => $customer->getCity() ?? '',
|
||||
$prefix . 'name' => $customer->getName() ?? '',
|
||||
$prefix . 'contact' => $customer->getContact() ?? '',
|
||||
$prefix . 'company' => $customer->getCompany() ?? '',
|
||||
$prefix . 'vat_id' => $customer->getVatId() ?? '',
|
||||
$prefix . 'number' => $customer->getNumber() ?? '',
|
||||
$prefix . 'country' => $country,
|
||||
$prefix . 'country_name' => $country !== null ? Countries::getName($country, $language) : null,
|
||||
$prefix . 'homepage' => $customer->getHomepage() ?? '',
|
||||
$prefix . 'comment' => $customer->getComment() ?? '',
|
||||
$prefix . 'email' => $customer->getEmail() ?? '',
|
||||
$prefix . 'fax' => $customer->getFax() ?? '',
|
||||
$prefix . 'phone' => $customer->getPhone() ?? '',
|
||||
$prefix . 'mobile' => $customer->getMobile() ?? '',
|
||||
$prefix . 'invoice_text' => $customer->getInvoiceText() ?? '',
|
||||
$prefix . 'buyer_reference' => $customer->getBuyerReference() ?? '',
|
||||
];
|
||||
|
||||
foreach ($customer->getMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
$prefix . 'meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,12 @@ use App\Invoice\Hydrator\InvoiceItemDefaultHydrator;
|
||||
use App\Invoice\Hydrator\InvoiceModelActivityHydrator;
|
||||
use App\Invoice\Hydrator\InvoiceModelCustomerHydrator;
|
||||
use App\Invoice\Hydrator\InvoiceModelDefaultHydrator;
|
||||
use App\Invoice\Hydrator\InvoiceModelIssuerHydrator;
|
||||
use App\Invoice\Hydrator\InvoiceModelProjectHydrator;
|
||||
use App\Invoice\Hydrator\InvoiceModelUserHydrator;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use App\Timesheet\RateCalculator\RateCalculatorMode;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Exclude;
|
||||
|
||||
/**
|
||||
@@ -68,12 +70,14 @@ final class InvoiceModel
|
||||
ActivityStatisticService $activityStatistic,
|
||||
private readonly Customer $customer,
|
||||
private readonly InvoiceTemplate $template,
|
||||
private readonly RateCalculatorMode $rateCalculatorMode
|
||||
)
|
||||
{
|
||||
$this->invoiceDate = new \DateTimeImmutable();
|
||||
$this->formatter = $formatter;
|
||||
$this->addModelHydrator(new InvoiceModelDefaultHydrator());
|
||||
$this->addModelHydrator(new InvoiceModelCustomerHydrator($customerStatistic));
|
||||
$this->addModelHydrator(new InvoiceModelIssuerHydrator());
|
||||
$this->addModelHydrator(new InvoiceModelProjectHydrator($projectStatistic));
|
||||
$this->addModelHydrator(new InvoiceModelActivityHydrator($activityStatistic));
|
||||
$this->addModelHydrator(new InvoiceModelUserHydrator());
|
||||
@@ -207,6 +211,11 @@ final class InvoiceModel
|
||||
return $this->calculator;
|
||||
}
|
||||
|
||||
public function getRateCalculatorMode(): RateCalculatorMode
|
||||
{
|
||||
return $this->rateCalculatorMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user currently creating the invoice.
|
||||
*/
|
||||
|
||||
@@ -15,19 +15,21 @@ use App\Entity\Customer;
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use App\Timesheet\RateCalculator\RateCalculatorMode;
|
||||
|
||||
final class InvoiceModelFactory
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CustomerStatisticService $customerStatisticService,
|
||||
private readonly ProjectStatisticService $projectStatisticService,
|
||||
private readonly ActivityStatisticService $activityStatisticService
|
||||
private readonly ActivityStatisticService $activityStatisticService,
|
||||
private readonly RateCalculatorMode $rateCalculatorMode
|
||||
) {
|
||||
}
|
||||
|
||||
public function createModel(InvoiceFormatter $formatter, Customer $customer, InvoiceTemplate $template, InvoiceQuery $query): InvoiceModel
|
||||
{
|
||||
$model = new InvoiceModel($formatter, $this->customerStatisticService, $this->projectStatisticService, $this->activityStatisticService, $customer, $template);
|
||||
$model = new InvoiceModel($formatter, $this->customerStatisticService, $this->projectStatisticService, $this->activityStatisticService, $customer, $template, $this->rateCalculatorMode);
|
||||
|
||||
$model->setQuery($query);
|
||||
|
||||
|
||||
@@ -49,11 +49,11 @@ final class ServiceInvoice
|
||||
private array $invoiceItemRepositories = [];
|
||||
|
||||
public function __construct(
|
||||
private InvoiceDocumentRepository $documents,
|
||||
private FileHelper $fileHelper,
|
||||
private InvoiceRepository $invoiceRepository,
|
||||
private LocaleService $formatter,
|
||||
private InvoiceModelFactory $invoiceModelFactory
|
||||
private readonly InvoiceDocumentRepository $documents,
|
||||
private readonly FileHelper $fileHelper,
|
||||
private readonly InvoiceRepository $invoiceRepository,
|
||||
private readonly LocaleService $formatter,
|
||||
private readonly InvoiceModelFactory $invoiceModelFactory
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use App\Timesheet\RateServiceInterface;
|
||||
*/
|
||||
final class RateCalculator implements CalculatorInterface
|
||||
{
|
||||
public function __construct(private RateServiceInterface $service)
|
||||
public function __construct(private readonly RateServiceInterface $service)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
31
src/Timesheet/RateCalculator/ClassicRateCalculator.php
Normal file
31
src/Timesheet/RateCalculator/ClassicRateCalculator.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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\Timesheet\RateCalculator;
|
||||
|
||||
final class ClassicRateCalculator implements RateCalculatorMode
|
||||
{
|
||||
/**
|
||||
* Calculates the rate by an hourly rate and a given duration in seconds.
|
||||
*/
|
||||
public function calculateRate(float $hourlyRate, int $seconds): float
|
||||
{
|
||||
$rate = $hourlyRate * ($seconds / 3600);
|
||||
|
||||
return round($rate, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not round the duration, we keep the original sum.
|
||||
*/
|
||||
public function roundDuration(int $seconds): int
|
||||
{
|
||||
return $seconds;
|
||||
}
|
||||
}
|
||||
33
src/Timesheet/RateCalculator/DecimalRateCalculator.php
Normal file
33
src/Timesheet/RateCalculator/DecimalRateCalculator.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?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\Timesheet\RateCalculator;
|
||||
|
||||
final class DecimalRateCalculator implements RateCalculatorMode
|
||||
{
|
||||
/**
|
||||
* Calculates the rate by an hourly rate and a given duration in seconds.
|
||||
*/
|
||||
public function calculateRate(float $hourlyRate, int $seconds): float
|
||||
{
|
||||
$rate = $hourlyRate * round(($seconds / 3600), 2, PHP_ROUND_HALF_UP);
|
||||
|
||||
return round($rate, 2, PHP_ROUND_HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure tha the duration is full compatible with decimal format, stripping away overflowing seconds.
|
||||
*/
|
||||
public function roundDuration(int $seconds): int
|
||||
{
|
||||
$decimal = round(($seconds / 3600), 2, PHP_ROUND_HALF_UP);
|
||||
|
||||
return (int) round(($decimal * 3600), 0, PHP_ROUND_HALF_UP);
|
||||
}
|
||||
}
|
||||
34
src/Timesheet/RateCalculator/RateCalculatorFactory.php
Normal file
34
src/Timesheet/RateCalculator/RateCalculatorFactory.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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\Timesheet\RateCalculator;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class RateCalculatorFactory
|
||||
{
|
||||
public function __construct(private readonly SystemConfiguration $configuration)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal do not call, use RateCalculatorMode injection
|
||||
*/
|
||||
public function getRateCalculatorMode(): RateCalculatorMode
|
||||
{
|
||||
if ($this->configuration->find('invoice.rounding_mode') === 'decimal') {
|
||||
return new DecimalRateCalculator();
|
||||
}
|
||||
|
||||
return new ClassicRateCalculator();
|
||||
}
|
||||
}
|
||||
23
src/Timesheet/RateCalculator/RateCalculatorMode.php
Normal file
23
src/Timesheet/RateCalculator/RateCalculatorMode.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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\Timesheet\RateCalculator;
|
||||
|
||||
interface RateCalculatorMode
|
||||
{
|
||||
/**
|
||||
* Calculates the rate by an hourly rate and a given duration in seconds.
|
||||
*/
|
||||
public function calculateRate(float $hourlyRate, int $seconds): float;
|
||||
|
||||
/**
|
||||
* * Makes sure tha the duration is fully rounded.
|
||||
*/
|
||||
public function roundDuration(int $seconds): int;
|
||||
}
|
||||
@@ -13,13 +13,18 @@ use App\Entity\RateInterface;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\UserPreference;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Timesheet\RateCalculator\RateCalculatorMode;
|
||||
|
||||
/**
|
||||
* Implementation to calculate the rate for a timesheet record.
|
||||
*/
|
||||
final class RateService implements RateServiceInterface
|
||||
{
|
||||
public function __construct(private array $rates, private TimesheetRepository $repository)
|
||||
public function __construct(
|
||||
private readonly array $rates,
|
||||
private readonly TimesheetRepository $repository,
|
||||
private readonly RateCalculatorMode $calculatorMode
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -79,8 +84,8 @@ final class RateService implements RateServiceInterface
|
||||
$totalInternalRate = 0;
|
||||
|
||||
if (null !== $record->getDuration()) {
|
||||
$totalRate = Util::calculateRate($factoredHourlyRate, $record->getDuration());
|
||||
$totalInternalRate = Util::calculateRate($factoredInternalRate, $record->getDuration());
|
||||
$totalRate = $this->calculatorMode->calculateRate($factoredHourlyRate, $record->getDuration());
|
||||
$totalInternalRate = $this->calculatorMode->calculateRate($factoredInternalRate, $record->getDuration());
|
||||
}
|
||||
|
||||
return new Rate($totalRate, $totalInternalRate, $factoredHourlyRate, null);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace App\Timesheet;
|
||||
|
||||
/**
|
||||
* A static helper class for re-usable functionality.
|
||||
* @deprecated use RateCalculatorMode instead
|
||||
*/
|
||||
final class Util
|
||||
{
|
||||
@@ -29,7 +29,7 @@ final class Util
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure tha the duration is full compatible with decimal format, stripping away overflowing seconds.
|
||||
* Makes sure that the duration is full compatible with decimal format, stripping away overflowing seconds.
|
||||
*/
|
||||
public static function decimalizeDuration(int $seconds): int
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user