Release 2.0.4 (#3883)

* fix column data truncated
* calculate internal rate from user
* show internal rate in timesheet listing
* Fixed: responsivenss and size of report start page icons
* fix: name display in dropdowns (and added tests)
* translate reload button
* fix invoice date might be in the past
* fail safe customer name handling
* translate invoice_date and invoice_date help
* prevent URLs like start=null
* prevent to reload select twice
This commit is contained in:
Kevin Papst
2023-03-02 14:04:06 +01:00
committed by GitHub
parent a8b972f8a5
commit 3f09a2674b
32 changed files with 409 additions and 77 deletions

View File

@@ -25,7 +25,7 @@ class ReportingControllerTest extends ControllerBaseTest
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->request($client, '/reporting/');
$nodes = $client->getCrawler()->filter('section.content div.card');
$nodes = $client->getCrawler()->filter('section.content div.row-cards a.card-link');
$this->assertCount(11, $nodes);
}
@@ -33,7 +33,7 @@ class ReportingControllerTest extends ControllerBaseTest
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->request($client, '/reporting/');
$nodes = $client->getCrawler()->filter('section.content div.card');
$nodes = $client->getCrawler()->filter('section.content div.row-cards a.card-link');
$this->assertCount(3, $nodes);
}
}

View File

@@ -0,0 +1,62 @@
<?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\Tests\Form\Helper;
use App\Entity\Activity;
use App\Form\Helper\ActivityHelper;
use App\Tests\Mocks\SystemConfigurationFactory;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Form\Helper\ActivityHelper
*/
class ActivityHelperTest extends TestCase
{
private function createSut(string $format): ActivityHelper
{
$config = SystemConfigurationFactory::createStub(['activity.choice_pattern' => $format]);
$helper = new ActivityHelper($config);
return $helper;
}
public function testInvalidPattern(): void
{
$helper = $this->createSut('sdfsdf');
self::assertEquals(ActivityHelper::PATTERN_NAME, $helper->getChoicePattern());
}
public function testGetChoicePattern(): void
{
$helper = $this->createSut(
ActivityHelper::PATTERN_NAME . ActivityHelper::PATTERN_SPACER .
ActivityHelper::PATTERN_COMMENT
);
self::assertEquals(
ActivityHelper::PATTERN_NAME . ActivityHelper::SPACER .
ActivityHelper::PATTERN_COMMENT,
$helper->getChoicePattern()
);
}
public function testGetChoiceLabel(): void
{
$helper = $this->createSut(ActivityHelper::PATTERN_NAME . ActivityHelper::PATTERN_SPACER . ActivityHelper::PATTERN_COMMENT);
$activity = new Activity();
$activity->setName(' - --- - -FOO BAR- --- - - - ');
self::assertEquals('--- - -FOO BAR- ---', $helper->getChoiceLabel($activity));
$activity->setName('FOO BAR');
$activity->setComment('Lorem Ipsum');
self::assertEquals('FOO BAR - Lorem Ipsum', $helper->getChoiceLabel($activity));
}
}

View File

@@ -0,0 +1,74 @@
<?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\Tests\Form\Helper;
use App\Entity\Customer;
use App\Form\Helper\CustomerHelper;
use App\Tests\Mocks\SystemConfigurationFactory;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Form\Helper\CustomerHelper
*/
class CustomerHelperTest extends TestCase
{
private function createSut(string $format): CustomerHelper
{
$config = SystemConfigurationFactory::createStub(['customer.choice_pattern' => $format]);
$helper = new CustomerHelper($config);
return $helper;
}
public function testInvalidPattern(): void
{
$helper = $this->createSut('sdfsdf');
self::assertEquals(CustomerHelper::PATTERN_NAME, $helper->getChoicePattern());
}
public function testGetChoicePattern(): void
{
$helper = $this->createSut(
CustomerHelper::PATTERN_NAME . CustomerHelper::PATTERN_SPACER .
CustomerHelper::PATTERN_COMMENT . CustomerHelper::PATTERN_SPACER .
CustomerHelper::PATTERN_COMPANY . CustomerHelper::PATTERN_SPACER .
CustomerHelper::PATTERN_NUMBER
);
self::assertEquals(
CustomerHelper::PATTERN_NAME . CustomerHelper::SPACER .
CustomerHelper::PATTERN_COMMENT . CustomerHelper::SPACER .
CustomerHelper::PATTERN_COMPANY . CustomerHelper::SPACER .
CustomerHelper::PATTERN_NUMBER,
$helper->getChoicePattern()
);
}
public function testGetChoiceLabel(): void
{
$helper = $this->createSut(
CustomerHelper::PATTERN_NAME . CustomerHelper::PATTERN_SPACER .
CustomerHelper::PATTERN_COMMENT . CustomerHelper::PATTERN_SPACER .
CustomerHelper::PATTERN_COMPANY . CustomerHelper::PATTERN_SPACER .
CustomerHelper::PATTERN_NUMBER
);
$customer = new Customer(' - --- - -FOO BAR- --- - - - ');
self::assertEquals('--- - -FOO BAR- ---', $helper->getChoiceLabel($customer));
$customer = new Customer('FOO BAR');
$customer->setComment('Lorem Ipsum');
self::assertEquals('FOO BAR - Lorem Ipsum', $helper->getChoiceLabel($customer));
$customer->setCompany('Acme University');
self::assertEquals('FOO BAR - Lorem Ipsum - Acme University', $helper->getChoiceLabel($customer));
$customer->setNumber('2023-0815');
self::assertEquals('FOO BAR - Lorem Ipsum - Acme University - 2023-0815', $helper->getChoiceLabel($customer));
}
}

View File

@@ -0,0 +1,109 @@
<?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\Tests\Form\Helper;
use App\Configuration\LocaleService;
use App\Entity\Customer;
use App\Entity\Project;
use App\Form\Helper\ProjectHelper;
use App\Tests\Mocks\SystemConfigurationFactory;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @covers \App\Form\Helper\ProjectHelper
*/
class ProjectHelperTest extends TestCase
{
private function createSut(string $format): ProjectHelper
{
$config = SystemConfigurationFactory::createStub(['project.choice_pattern' => $format]);
$localeService = new LocaleService(['en_US' => ['date' => 'dd.MM.y']]);
$translator = $this->createMock(TranslatorInterface::class);
$translator->method('trans')->willReturn('dating');
$helper = new ProjectHelper($config, $localeService, $translator);
$helper->setLocale('en_US');
return $helper;
}
public function testInvalidPattern(): void
{
$helper = $this->createSut('sdfsdf');
self::assertEquals(ProjectHelper::PATTERN_NAME, $helper->getChoicePattern());
}
public function testGetChoicePattern(): void
{
$helper = $this->createSut(
ProjectHelper::PATTERN_NAME . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_COMMENT . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_CUSTOMER . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_ORDERNUMBER . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_START . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_END . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_DATERANGE
);
self::assertEquals(
ProjectHelper::PATTERN_NAME . ProjectHelper::SPACER .
ProjectHelper::PATTERN_COMMENT . ProjectHelper::SPACER .
ProjectHelper::PATTERN_CUSTOMER . ProjectHelper::SPACER .
ProjectHelper::PATTERN_ORDERNUMBER . ProjectHelper::SPACER .
ProjectHelper::PATTERN_START . ProjectHelper::SPACER .
ProjectHelper::PATTERN_END . ProjectHelper::SPACER .
ProjectHelper::PATTERN_START . '-' . ProjectHelper::PATTERN_END,
$helper->getChoicePattern()
);
}
public function testGetChoiceLabel(): void
{
$helper = $this->createSut(
ProjectHelper::PATTERN_NAME . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_COMMENT . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_CUSTOMER . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_ORDERNUMBER
);
$project = new Project();
$project->setName(' - --- - -FOO BAR- --- - - - ');
$customer = new Customer(' - --- - - Acme company- --- - - - ');
$project->setCustomer($customer);
self::assertEquals('--- - -FOO BAR- --- - - - - - - --- - - Acme company- ---', $helper->getChoiceLabel($project));
$project = new Project();
$project->setName('FOO BAR');
$customer = new Customer('Acme company');
$project->setCustomer($customer);
$project->setComment('Lorem Ipsum');
$project->setOrderNumber('F76/123');
self::assertEquals('FOO BAR - Lorem Ipsum - Acme company - F76/123', $helper->getChoiceLabel($project));
}
public function testGetChoiceLabelWithDates(): void
{
$helper = $this->createSut(
ProjectHelper::PATTERN_NAME . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_START . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_END . ProjectHelper::PATTERN_SPACER .
ProjectHelper::PATTERN_DATERANGE
);
$project = new Project();
$project->setName('FOO BAR');
self::assertEquals('FOO BAR - - - -', $helper->getChoiceLabel($project));
$project->setStart(new \DateTime('2018-12-27 18:45:12'));
self::assertEquals('FOO BAR - dating: 27.12.2018 - - dating: 27.12.2018-', $helper->getChoiceLabel($project));
$project->setEnd(new \DateTime('2019-02-14 01:23:45'));
self::assertEquals('FOO BAR - dating: 27.12.2018 - dating: 14.02.2019 - dating: 27.12.2018-dating: 14.02.2019', $helper->getChoiceLabel($project));
}
}

View File

@@ -76,25 +76,25 @@ class RateCalculatorTest extends TestCase
yield 'a6' => [0.5, 6.72, 1800, 1, 13.44, null, null, null, null, false, null, null, false, null, null, false];
yield 'a8' => [0.0, 1, 0, 0, 0, 0, 0, 0, 1, true, 0, null, true, 0, null, true];
// rate: 1.5 => timesheet hourly rate , internal: 2.5 => activity hourly rate (30 min)
yield 'b1' => [1.5, 2.5, 1800, 1, 1, 3, null, 5, null, false, 7, null, false, 9, null, false];
yield 'b2' => [2.5, 2.5, 1800, 1, 1, null, null, 5, null, false, 7, null, false, 9, null, false];
yield 'b1' => [1.5, 0.5, 1800, 1, 1, 3, null, 5, null, false, 7, null, false, 9, null, false];
yield 'b2' => [2.5, 4.5, 1800, 1, 9, null, null, 5, null, false, 7, null, false, 9, null, false];
yield 'b3' => [3.5, 6.5, 1800, 1, 1, null, null, null, null, false, 7, 13, false, 9, 9, false];
yield 'b4' => [4.5, 6.5, 1800, 1, 15, null, null, null, null, false, null, null, false, 9, 13, false];
// rate: 2.0 => timesheet fixed rate , internal: 3.0 => activity fixed rate
yield 'b5' => [2.0, 3.0, 1800, 1, 1, null, 2, 3, null, true, 4, null, true, 5, null, true];
yield 'b6' => [3.0, 3.0, 1800, 1, 1, null, null, 3, null, true, 4, null, true, 5, null, true];
yield 'b7' => [4.0, 4.0, 1800, 1, 1, null, null, null, null, false, 4, null, true, 5, null, true];
yield 'b8' => [3.0, 3.0, 1800, 1, 1, null, null, 3, null, true, null, null, false, 5, null, true];
yield 'b5' => [2.0, 1.0, 1800, 1, 1, null, 2, 3, null, true, 4, null, true, 5, null, true];
yield 'b6' => [3.0, 3.0, 1800, 1, 3, null, null, 3, null, true, 4, null, true, 5, null, true];
yield 'b7' => [4.0, 7.0, 1800, 1, 7, null, null, null, null, false, 4, null, true, 5, null, true];
yield 'b8' => [3.0, 4.7, 1800, 1, 4.7, null, null, 3, null, true, null, null, false, 5, null, true];
// rate: 2.0 => timesheet fixed rate , internal: 5.0 => customer hourly rate
yield 'b9' => [2.0, 5.0, 1800, 1, 1, null, 2, null, null, false, null, null, false, 5, null, true];
yield 'b9' => [2.0, 71.0, 1800, 1, 71, null, 2, null, null, false, null, null, false, 5, null, true];
// rate: 5.0 => timesheet hourly rate , internal: 7.5 => user internal rate (30 min)
yield 'c0' => [5.0, 7.5, 1800, 100, 15, 10, null, null, null, false, null, null, false, null, null, false];
// internal: 10 because no rule applies and as fallback the users internal rate is used
yield 'd0' => [10, 100, 1800, 100, 100, null, 10, null, null, false, null, null, false, null, null, false];
yield 'e0' => [10, 10, 1800, 100, 100, null, null, 20, null, false, null, null, false, null, null, false];
yield 'e0' => [10, 50, 1800, 100, 100, null, null, 20, null, false, null, null, false, null, null, false];
yield 'f0' => [20, 78, 1800, 100, 100, null, null, 20, 78, true, null, null, false, null, null, false];
yield 'g0' => [15, 11.5, 1800, 100, 100, null, null, null, null, false, 30, 23, false, null, null, false];
yield 'h0' => [30, 30, 1800, 100, 100, null, null, null, null, false, 30, null, true, null, null, false];
yield 'h0' => [30, 100, 1800, 100, 100, null, null, null, null, false, 30, null, true, null, null, false];
yield 'i0' => [20, 13.5, 1800, 100, 100, null, null, null, null, false, null, null, false, 40, 27, false];
yield 'j0' => [40, 84, 1800, 100, 45, null, null, null, null, false, null, null, false, 40, 84, true];
// make sure the last fallback for the internal rate is the users hourly rate

View File

@@ -80,25 +80,25 @@ class RateServiceTest extends TestCase
yield 'a6' => [0.5, 6.72, 1800, 1, 13.44, null, null, null, null, false, null, null, false, null, null, false];
yield 'a8' => [0.0, 1, 0, 0, 0, 0, 0, 0, 1, true, 0, null, true, 0, null, true];
// rate: 1.5 => timesheet hourly rate , internal: 2.5 => activity hourly rate (30 min)
yield 'b1' => [1.5, 2.5, 1800, 1, 1, 3, null, 5, null, false, 7, null, false, 9, null, false];
yield 'b2' => [2.5, 2.5, 1800, 1, 1, null, null, 5, null, false, 7, null, false, 9, null, false];
yield 'b1' => [1.5, 0.5, 1800, 1, 1, 3, null, 5, null, false, 7, null, false, 9, null, false];
yield 'b2' => [2.5, 4.5, 1800, 1, 9, null, null, 5, null, false, 7, null, false, 9, null, false];
yield 'b3' => [3.5, 6.5, 1800, 1, 1, null, null, null, null, false, 7, 13, false, 9, 9, false];
yield 'b4' => [4.5, 6.5, 1800, 1, 15, null, null, null, null, false, null, null, false, 9, 13, false];
// rate: 2.0 => timesheet fixed rate , internal: 3.0 => activity fixed rate
yield 'b5' => [2.0, 3.0, 1800, 1, 1, null, 2, 3, null, true, 4, null, true, 5, null, true];
yield 'b6' => [3.0, 3.0, 1800, 1, 1, null, null, 3, null, true, 4, null, true, 5, null, true];
yield 'b7' => [4.0, 4.0, 1800, 1, 1, null, null, null, null, false, 4, null, true, 5, null, true];
yield 'b8' => [3.0, 3.0, 1800, 1, 1, null, null, 3, null, true, null, null, false, 5, null, true];
yield 'b5' => [2.0, 1.0, 1800, 1, 1, null, 2, 3, null, true, 4, null, true, 5, null, true];
yield 'b6' => [3.0, 3.0, 1800, 1, 3, null, null, 3, null, true, 4, null, true, 5, null, true];
yield 'b7' => [4.0, 7.0, 1800, 1, 7, null, null, null, null, false, 4, null, true, 5, null, true];
yield 'b8' => [3.0, 4.7, 1800, 1, 4.7, null, null, 3, null, true, null, null, false, 5, null, true];
// rate: 2.0 => timesheet fixed rate , internal: 5.0 => customer hourly rate
yield 'b9' => [2.0, 5.0, 1800, 1, 1, null, 2, null, null, false, null, null, false, 5, null, true];
yield 'b9' => [2.0, 71.0, 1800, 1, 71, null, 2, null, null, false, null, null, false, 5, null, true];
// rate: 5.0 => timesheet hourly rate , internal: 7.5 => user internal rate (30 min)
yield 'c0' => [5.0, 7.5, 1800, 100, 15, 10, null, null, null, false, null, null, false, null, null, false];
// internal: 10 because no rule applies and as fallback the users internal rate is used
yield 'd0' => [10, 100, 1800, 100, 100, null, 10, null, null, false, null, null, false, null, null, false];
yield 'e0' => [10, 10, 1800, 100, 100, null, null, 20, null, false, null, null, false, null, null, false];
yield 'e0' => [10, 50, 1800, 100, 100, null, null, 20, null, false, null, null, false, null, null, false];
yield 'f0' => [20, 78, 1800, 100, 100, null, null, 20, 78, true, null, null, false, null, null, false];
yield 'g0' => [15, 11.5, 1800, 100, 100, null, null, null, null, false, 30, 23, false, null, null, false];
yield 'h0' => [30, 30, 1800, 100, 100, null, null, null, null, false, 30, null, true, null, null, false];
yield 'h0' => [30, 100, 1800, 100, 100, null, null, null, null, false, 30, null, true, null, null, false];
yield 'i0' => [20, 13.5, 1800, 100, 100, null, null, null, null, false, null, null, false, 40, 27, false];
yield 'j0' => [40, 84, 1800, 100, 45, null, null, null, null, false, null, null, false, 40, 84, true];
// make sure the last fallback for the internal rate is the users hourly rate