1.18 (#3158)
* bump version * fix translation ids * added css classes to modify form with custom css * improve export pdf file names * respect financial year in new report * added new InvoiceCalculator: price * upgrade packages and node-sass to v7 * title pattern for customer, project and activity via API * support negative money without currency * fix sub-locale in print export template * fix overbooking validation for monthly budget * fix copying entities with different set of custom-fields compared to the current configuration
This commit is contained in:
@@ -223,8 +223,9 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
|
||||
$this->assertTrue($form->has('activity_edit_form[metaFields][0][value]'));
|
||||
$this->assertFalse($form->has('activity_edit_form[metaFields][1][value]'));
|
||||
$this->assertTrue($form->has('activity_edit_form[metaFields][metatestmock][value]'));
|
||||
$this->assertTrue($form->has('activity_edit_form[metaFields][foobar][value]'));
|
||||
$this->assertFalse($form->has('activity_edit_form[metaFields][0][value]'));
|
||||
}
|
||||
|
||||
public function testEditAction()
|
||||
|
||||
@@ -367,8 +367,9 @@ class CustomerControllerTest extends ControllerBaseTest
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=customer_edit_form]')->form();
|
||||
$this->assertTrue($form->has('customer_edit_form[metaFields][0][value]'));
|
||||
$this->assertFalse($form->has('customer_edit_form[metaFields][1][value]'));
|
||||
$this->assertTrue($form->has('customer_edit_form[metaFields][metatestmock][value]'));
|
||||
$this->assertTrue($form->has('customer_edit_form[metaFields][foobar][value]'));
|
||||
$this->assertFalse($form->has('customer_edit_form[metaFields][0][value]'));
|
||||
}
|
||||
|
||||
public function testEditAction()
|
||||
|
||||
@@ -435,8 +435,9 @@ class ProjectControllerTest extends ControllerBaseTest
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=project_edit_form]')->form();
|
||||
$this->assertTrue($form->has('project_edit_form[metaFields][0][value]'));
|
||||
$this->assertFalse($form->has('project_edit_form[metaFields][1][value]'));
|
||||
$this->assertTrue($form->has('project_edit_form[metaFields][metatestmock][value]'));
|
||||
$this->assertTrue($form->has('project_edit_form[metaFields][foobar][value]'));
|
||||
$this->assertFalse($form->has('project_edit_form[metaFields][0][value]'));
|
||||
}
|
||||
|
||||
public function testEditAction()
|
||||
|
||||
@@ -257,8 +257,9 @@ class TimesheetControllerTest extends ControllerBaseTest
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=timesheet_edit_form]')->form();
|
||||
$this->assertTrue($form->has('timesheet_edit_form[metaFields][0][value]'));
|
||||
$this->assertFalse($form->has('timesheet_edit_form[metaFields][1][value]'));
|
||||
$this->assertTrue($form->has('timesheet_edit_form[metaFields][metatestmock][value]'));
|
||||
$this->assertTrue($form->has('timesheet_edit_form[metaFields][foobar][value]'));
|
||||
$this->assertFalse($form->has('timesheet_edit_form[metaFields][0][value]'));
|
||||
}
|
||||
|
||||
public function testCreateActionDoesNotShowRateFieldsForUser()
|
||||
|
||||
143
tests/Invoice/Calculator/PriceInvoiceCalculatorTest.php
Normal file
143
tests/Invoice/Calculator/PriceInvoiceCalculatorTest.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?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\Invoice\Calculator;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Invoice\Calculator\DateInvoiceCalculator;
|
||||
use App\Invoice\Calculator\PriceInvoiceCalculator;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use App\Tests\Invoice\DebugFormatter;
|
||||
use App\Tests\Mocks\InvoiceModelFactoryFactory;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* @covers \App\Invoice\Calculator\PriceInvoiceCalculator
|
||||
* @covers \App\Invoice\Calculator\AbstractSumInvoiceCalculator
|
||||
* @covers \App\Invoice\Calculator\AbstractMergedCalculator
|
||||
* @covers \App\Invoice\Calculator\AbstractCalculator
|
||||
*/
|
||||
class PriceInvoiceCalculatorTest extends AbstractCalculatorTest
|
||||
{
|
||||
public function testEmptyModel()
|
||||
{
|
||||
$this->assertEmptyModel(new DateInvoiceCalculator());
|
||||
}
|
||||
|
||||
public function testWithMultipleEntries()
|
||||
{
|
||||
$customer = new Customer();
|
||||
$template = new InvoiceTemplate();
|
||||
$template->setVat(19);
|
||||
|
||||
$user = $this->getMockBuilder(User::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
|
||||
$user->method('getId')->willReturn(1);
|
||||
|
||||
$project1 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
|
||||
$project1->method('getId')->willReturn(1);
|
||||
|
||||
$project2 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
|
||||
$project2->method('getId')->willReturn(2);
|
||||
|
||||
$project3 = $this->getMockBuilder(Project::class)->onlyMethods(['getId'])->disableOriginalConstructor()->getMock();
|
||||
$project3->method('getId')->willReturn(3);
|
||||
|
||||
$timesheet = new Timesheet();
|
||||
$timesheet
|
||||
->setBegin(new DateTime('2018-11-29'))
|
||||
->setEnd(new DateTime())
|
||||
->setDuration(3600)
|
||||
->setHourlyRate(293.27)
|
||||
->setRate(293.27)
|
||||
->setUser($user)
|
||||
->setActivity((new Activity())->setName('sdsd'))
|
||||
->setProject($project1);
|
||||
|
||||
$timesheet2 = new Timesheet();
|
||||
$timesheet2
|
||||
->setBegin(new DateTime('2018-11-29'))
|
||||
->setEnd(new DateTime())
|
||||
->setDuration(400)
|
||||
->setHourlyRate(293.27)
|
||||
->setRate(84.75)
|
||||
->setUser($user)
|
||||
->setActivity((new Activity())->setName('bar'))
|
||||
->setProject($project2);
|
||||
|
||||
$timesheet3 = new Timesheet();
|
||||
$timesheet3
|
||||
->setBegin(new DateTime('2018-11-28'))
|
||||
->setEnd(new DateTime())
|
||||
->setDuration(1800)
|
||||
->setFixedRate(111.11)
|
||||
->setRate(111.11)
|
||||
->setUser($user)
|
||||
->setActivity((new Activity())->setName('foo'))
|
||||
->setProject($project1);
|
||||
|
||||
$timesheet4 = new Timesheet();
|
||||
$timesheet4
|
||||
->setBegin(new DateTime())
|
||||
->setEnd(new DateTime('2018-11-28'))
|
||||
->setDuration(400)
|
||||
->setHourlyRate(0)
|
||||
->setRate(1947.99)
|
||||
->setUser($user)
|
||||
->setActivity((new Activity())->setName('blub'))
|
||||
->setProject($project2);
|
||||
|
||||
$timesheet5 = new Timesheet();
|
||||
$timesheet5
|
||||
->setBegin(new DateTime('2018-11-28'))
|
||||
->setEnd(new DateTime())
|
||||
->setDuration(400)
|
||||
->setRate(84)
|
||||
->setUser(new User())
|
||||
->setActivity(new Activity())
|
||||
->setProject($project3);
|
||||
|
||||
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
|
||||
|
||||
$query = new InvoiceQuery();
|
||||
$query->setProjects([$project1]);
|
||||
|
||||
$model = (new InvoiceModelFactoryFactory($this))->create()->createModel(new DebugFormatter());
|
||||
$model->setCustomer($customer);
|
||||
$model->setTemplate($template);
|
||||
$model->addEntries($entries);
|
||||
$model->setQuery($query);
|
||||
|
||||
$sut = new PriceInvoiceCalculator();
|
||||
$sut->setModel($model);
|
||||
|
||||
$this->assertEquals('price', $sut->getId());
|
||||
$this->assertEquals(3000.13, $sut->getTotal());
|
||||
$this->assertEquals(19, $sut->getVat());
|
||||
$this->assertEquals('EUR', $model->getCurrency());
|
||||
$this->assertEquals(2521.12, $sut->getSubtotal());
|
||||
$this->assertEquals(4800, $sut->getTimeWorked());
|
||||
|
||||
$entries = $sut->getEntries();
|
||||
self::assertCount(4, $entries);
|
||||
$this->assertEquals(378.02, $entries[0]->getRate());
|
||||
$this->assertEquals(111.11, $entries[1]->getRate());
|
||||
$this->assertEquals(1947.99, $entries[2]->getRate());
|
||||
$this->assertEquals(84, $entries[3]->getRate());
|
||||
}
|
||||
|
||||
public function testDescriptionByTimesheet()
|
||||
{
|
||||
$this->assertDescription(new PriceInvoiceCalculator(), false, false);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ namespace App\Tests\Mocks;
|
||||
use App\Entity\ActivityMeta;
|
||||
use App\Event\ActivityMetaDefinitionEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
@@ -33,5 +34,12 @@ class ActivityTestMetaFieldSubscriberMock implements EventSubscriberInterface
|
||||
->setIsVisible(true);
|
||||
|
||||
$event->getEntity()->setMetaField($definition);
|
||||
|
||||
$definition = (new ActivityMeta())
|
||||
->setName('foobar')
|
||||
->setType(IntegerType::class)
|
||||
->setIsVisible(false);
|
||||
|
||||
$event->getEntity()->setMetaField($definition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace App\Tests\Mocks;
|
||||
use App\Entity\CustomerMeta;
|
||||
use App\Event\CustomerMetaDefinitionEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
@@ -33,5 +34,12 @@ class CustomerTestMetaFieldSubscriberMock implements EventSubscriberInterface
|
||||
->setIsVisible(true);
|
||||
|
||||
$event->getEntity()->setMetaField($definition);
|
||||
|
||||
$definition = (new CustomerMeta())
|
||||
->setName('foobar')
|
||||
->setType(IntegerType::class)
|
||||
->setIsVisible(false);
|
||||
|
||||
$event->getEntity()->setMetaField($definition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace App\Tests\Mocks;
|
||||
use App\Entity\ProjectMeta;
|
||||
use App\Event\ProjectMetaDefinitionEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
@@ -33,5 +34,12 @@ class ProjectTestMetaFieldSubscriberMock implements EventSubscriberInterface
|
||||
->setIsVisible(true);
|
||||
|
||||
$event->getEntity()->setMetaField($definition);
|
||||
|
||||
$definition = (new ProjectMeta())
|
||||
->setName('foobar')
|
||||
->setType(IntegerType::class)
|
||||
->setIsVisible(false);
|
||||
|
||||
$event->getEntity()->setMetaField($definition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace App\Tests\Mocks;
|
||||
use App\Entity\TimesheetMeta;
|
||||
use App\Event\TimesheetMetaDefinitionEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
@@ -33,5 +34,11 @@ class TimesheetTestMetaFieldSubscriberMock implements EventSubscriberInterface
|
||||
->setIsVisible(true);
|
||||
|
||||
$event->getEntity()->setMetaField($definition);
|
||||
$definition = (new TimesheetMeta())
|
||||
->setName('foobar')
|
||||
->setType(IntegerType::class)
|
||||
->setIsVisible(false);
|
||||
|
||||
$event->getEntity()->setMetaField($definition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use Twig\TwigTest;
|
||||
|
||||
/**
|
||||
* @covers \App\Twig\Extensions
|
||||
@@ -28,6 +29,18 @@ class ExtensionsTest extends TestCase
|
||||
return new Extensions();
|
||||
}
|
||||
|
||||
private function getTest(string $name): TwigTest
|
||||
{
|
||||
$sut = $this->getSut();
|
||||
foreach ($sut->getTests() as $test) {
|
||||
if ($test->getName() === $name) {
|
||||
return $test;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \Exception('Unknown twig test: ' . $name);
|
||||
}
|
||||
|
||||
public function testGetFilters()
|
||||
{
|
||||
$filters = ['report_date', 'docu_link', 'multiline_indent', 'color', 'font_contrast', 'default_color', 'nl2str'];
|
||||
@@ -63,6 +76,22 @@ class ExtensionsTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetTests()
|
||||
{
|
||||
$tests = ['number'];
|
||||
$i = 0;
|
||||
|
||||
$sut = $this->getSut();
|
||||
$twigTests = $sut->getTests();
|
||||
$this->assertCount(\count($tests), $twigTests);
|
||||
|
||||
/** @var TwigTest $test */
|
||||
foreach ($twigTests as $test) {
|
||||
$this->assertInstanceOf(TwigTest::class, $test);
|
||||
$this->assertEquals($tests[$i++], $test->getName());
|
||||
}
|
||||
}
|
||||
|
||||
public function testDocuLink()
|
||||
{
|
||||
$data = [
|
||||
@@ -213,6 +242,17 @@ sdfsdf' . PHP_EOL . "\n" .
|
||||
self::assertEquals('', $sut->defaultColor(''));
|
||||
}
|
||||
|
||||
public function testIsNumeric()
|
||||
{
|
||||
$test = $this->getTest('number');
|
||||
self::assertFalse(\call_user_func($test->getCallable(), null));
|
||||
self::assertFalse(\call_user_func($test->getCallable(), true));
|
||||
self::assertFalse(\call_user_func($test->getCallable(), false));
|
||||
self::assertFalse(\call_user_func($test->getCallable(), '1'));
|
||||
self::assertTrue(\call_user_func($test->getCallable(), 1));
|
||||
self::assertTrue(\call_user_func($test->getCallable(), 1.0));
|
||||
}
|
||||
|
||||
private static function assertIsValidColor(string $color)
|
||||
{
|
||||
self::assertStringStartsWith('#', $color);
|
||||
|
||||
@@ -27,10 +27,10 @@ use Twig\TwigTest;
|
||||
*/
|
||||
class LocaleFormatExtensionsTest extends TestCase
|
||||
{
|
||||
private $localeEn = ['en' => ['date' => 'Y-m-d', 'duration' => '%h:%m h']];
|
||||
private $localeDe = ['de' => ['date' => 'd.m.Y', 'duration' => '%h:%m h']];
|
||||
private $localeRu = ['ru' => ['date' => 'd.m.Y', 'duration' => '%h:%m h']];
|
||||
private $localeFake = ['XX' => ['date' => 'd.m.Y', 'duration' => '%h - %m - %s Zeit']];
|
||||
private $localeEn = ['en' => ['date' => 'Y-m-d', 'duration' => '%h:%m h', 'date_type' => 'yyyy-MM-dd']];
|
||||
private $localeDe = ['de' => ['date' => 'd.m.Y', 'duration' => '%h:%m h', 'date_type' => 'yyyy-MM-dd']];
|
||||
private $localeRu = ['ru' => ['date' => 'd.m.Y', 'duration' => '%h:%m h', 'date_type' => 'yyyy-MM-dd']];
|
||||
private $localeFake = ['XX' => ['date' => 'd.m.Y', 'duration' => '%h - %m - %s Zeit', 'date_type' => 'yyyy-MM-dd']];
|
||||
|
||||
/**
|
||||
* @param string|array $locale
|
||||
@@ -74,7 +74,7 @@ class LocaleFormatExtensionsTest extends TestCase
|
||||
|
||||
public function testGetFunctions()
|
||||
{
|
||||
$functions = ['get_format_duration', 'create_date', 'locales', 'month_names'];
|
||||
$functions = ['javascript_configurations', 'get_format_duration', 'create_date', 'locales', 'month_names'];
|
||||
$i = 0;
|
||||
|
||||
$sut = $this->getSut('de', []);
|
||||
@@ -479,6 +479,19 @@ class LocaleFormatExtensionsTest extends TestCase
|
||||
self::assertEquals(342.94, $sut->durationChart(1234567));
|
||||
}
|
||||
|
||||
public function testJavascriptConfigurations()
|
||||
{
|
||||
$expected = [
|
||||
'formatDuration' => '%h:%m h',
|
||||
'formatDate' => 'YYYY-MM-DD',
|
||||
'defaultColor' => '#d2d6de',
|
||||
'twentyFourHours' => true,
|
||||
'updateBrowserTitle' => false,
|
||||
];
|
||||
$sut = $this->getSut('en', $this->localeEn);
|
||||
self::assertEquals($expected, $sut->getJavascriptConfiguration(new User()));
|
||||
}
|
||||
|
||||
public function testDurationDecimal()
|
||||
{
|
||||
$record = $this->getTimesheet(9437);
|
||||
|
||||
@@ -81,6 +81,8 @@ class LocaleHelperTest extends TestCase
|
||||
$this->assertEquals('123.234,76', $sut->money(123234.7554, null, true));
|
||||
$this->assertEquals('123.234,76', $sut->money(123234.7554, null, false));
|
||||
$this->assertEquals('123.234,76', $sut->money(123234.7554, 'EUR', false));
|
||||
$this->assertEquals('-123.234,76', $sut->money(-123234.7554, null, false));
|
||||
$this->assertEquals('-123.234,76', $sut->money(-123234.7554, 'EUR', false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +107,7 @@ class LocaleHelperTest extends TestCase
|
||||
['13,75', 13.75, 'RUB', 'de'],
|
||||
['13,75', 13.75, 'JPY', 'de'],
|
||||
['13 933,49', 13933.49, 'JPY', 'ru'],
|
||||
['-13 933,49', -13933.49, 'JPY', 'ru'],
|
||||
['13,75', 13.75, 'CNY', 'de'],
|
||||
['13.933,00', 13933, 'CNY', 'de'],
|
||||
['13 933,00', 13933, 'CNY', 'ru'],
|
||||
@@ -141,6 +144,8 @@ class LocaleHelperTest extends TestCase
|
||||
['13 933,00 CN¥', 13933, 'CNY', 'ru'],
|
||||
['CN¥13,933.00', 13933, 'CNY', 'en'],
|
||||
['1.234.567,89 $', 1234567.891234567890000, 'USD', 'de'],
|
||||
['-CN¥13,933.00', -13933, 'CNY', 'en'],
|
||||
['-1.234.567,89 $', -1234567.891234567890000, 'USD', 'de'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user