Release 2.30 (#5345)
- added missing InvoiceTemplate company, title) field validator - graceful fallback for missing working-contract mode - improve email test command (use configured MAIL_FROM) - additional form types for simple usage in SystemConfiguration and UserPreferences - allow to extend the working time query via event
This commit is contained in:
@@ -43,6 +43,7 @@ $fixer
|
|||||||
'single_line_after_imports' => true,
|
'single_line_after_imports' => true,
|
||||||
'switch_case_semicolon_to_colon' => true,
|
'switch_case_semicolon_to_colon' => true,
|
||||||
'switch_case_space' => true,
|
'switch_case_space' => true,
|
||||||
|
'php_unit_method_casing' => true,
|
||||||
'array_syntax' => [
|
'array_syntax' => [
|
||||||
'syntax' => 'short'
|
'syntax' => 'short'
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Constants;
|
||||||
use App\Event\EmailEvent;
|
use App\Event\EmailEvent;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
@@ -19,7 +20,7 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Mime\Email;
|
use Symfony\Component\Mime\Email;
|
||||||
|
|
||||||
#[AsCommand(name: 'kimai:mail:test', description: 'Send a test email')]
|
#[AsCommand(name: 'kimai:mail:test', description: 'Send a test email using MAILER_URL and MAILER_FROM')]
|
||||||
final class MailTestCommand extends Command
|
final class MailTestCommand extends Command
|
||||||
{
|
{
|
||||||
public function __construct(private readonly EventDispatcherInterface $dispatcher)
|
public function __construct(private readonly EventDispatcherInterface $dispatcher)
|
||||||
@@ -30,16 +31,24 @@ final class MailTestCommand extends Command
|
|||||||
protected function configure(): void
|
protected function configure(): void
|
||||||
{
|
{
|
||||||
$this->addArgument('to', InputArgument::REQUIRED, 'The email address to send the email to');
|
$this->addArgument('to', InputArgument::REQUIRED, 'The email address to send the email to');
|
||||||
$this->addOption('from', null, InputOption::VALUE_OPTIONAL, 'The sender of the message', 'kimai@example.org');
|
$this->addOption('from', null, InputOption::VALUE_OPTIONAL, 'Deprecated: uses the MAILER_FROM env variable.');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
|
$to = $input->getArgument('to');
|
||||||
|
if (!\is_string($to) || $to === '') {
|
||||||
|
throw new \InvalidArgumentException('Need a non-empty "to" address');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('from') !== null) {
|
||||||
|
throw new \InvalidArgumentException('The "from" option is deprecated and will be ignored');
|
||||||
|
}
|
||||||
|
|
||||||
$message = new Email();
|
$message = new Email();
|
||||||
$message->to((string) $input->getArgument('to')); // @phpstan-ignore-line
|
$message->to($to);
|
||||||
$message->from((string) $input->getOption('from')); // @phpstan-ignore-line
|
$message->subject('Test email - ' . Constants::SOFTWARE);
|
||||||
$message->subject('Kimai test email');
|
$message->text('This is a test email from your time-tracker');
|
||||||
$message->text('This is an email for testing the text body.');
|
|
||||||
|
|
||||||
$this->dispatcher->dispatch(new EmailEvent($message));
|
$this->dispatcher->dispatch(new EmailEvent($message));
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ final class Constants
|
|||||||
/**
|
/**
|
||||||
* The current release version
|
* The current release version
|
||||||
*/
|
*/
|
||||||
public const VERSION = '2.29.0';
|
public const VERSION = '2.30.0';
|
||||||
/**
|
/**
|
||||||
* The current release: major * 10000 + minor * 100 + patch
|
* The current release: major * 10000 + minor * 100 + patch
|
||||||
*/
|
*/
|
||||||
public const VERSION_ID = 22900;
|
public const VERSION_ID = 23000;
|
||||||
/**
|
/**
|
||||||
* The software name
|
* The software name
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ class InvoiceTemplate
|
|||||||
#[Assert\Length(min: 1, max: 60)]
|
#[Assert\Length(min: 1, max: 60)]
|
||||||
private ?string $name = null;
|
private ?string $name = null;
|
||||||
#[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)]
|
#[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)]
|
||||||
|
#[Assert\Length(max: 255)]
|
||||||
#[Assert\NotBlank]
|
#[Assert\NotBlank]
|
||||||
private ?string $title = null;
|
private ?string $title = null;
|
||||||
#[ORM\Column(name: 'company', type: 'string', length: 255, nullable: false)]
|
#[ORM\Column(name: 'company', type: 'string', length: 255, nullable: false)]
|
||||||
|
#[Assert\Length(max: 255)]
|
||||||
#[Assert\NotBlank]
|
#[Assert\NotBlank]
|
||||||
private ?string $company = null;
|
private ?string $company = null;
|
||||||
#[ORM\Column(name: 'vat_id', type: 'string', length: 50, nullable: true)]
|
#[ORM\Column(name: 'vat_id', type: 'string', length: 50, nullable: true)]
|
||||||
|
|||||||
46
src/Event/WorkingTimeQueryStatsEvent.php
Normal file
46
src/Event/WorkingTimeQueryStatsEvent.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?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\Event;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Contracts\EventDispatcher\Event;
|
||||||
|
|
||||||
|
final class WorkingTimeQueryStatsEvent extends Event
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly QueryBuilder $queryBuilder,
|
||||||
|
private readonly User $user,
|
||||||
|
private readonly \DateTimeInterface $begin,
|
||||||
|
private readonly \DateTimeInterface $end
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryBuilder(): QueryBuilder
|
||||||
|
{
|
||||||
|
return $this->queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBegin(): \DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEnd(): \DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->end;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/Form/DataTransformer/EntityByIdTransformer.php
Normal file
38
src/Form/DataTransformer/EntityByIdTransformer.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\Form\DataTransformer;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Symfony\Component\Form\DataTransformerInterface;
|
||||||
|
|
||||||
|
final class EntityByIdTransformer implements DataTransformerInterface // @phpstan-ignore missingType.generics
|
||||||
|
{
|
||||||
|
public function __construct(private readonly EntityRepository $repository) // @phpstan-ignore missingType.generics
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transform(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
return $this->repository->find($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseTransform(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (\is_object($value) && method_exists($value, 'getId') && $value->getId() !== null) {
|
||||||
|
return (string) $value->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/Form/Type/ActivityByIdType.php
Normal file
32
src/Form/Type/ActivityByIdType.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?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\Form\Type;
|
||||||
|
|
||||||
|
use App\Form\DataTransformer\EntityByIdTransformer;
|
||||||
|
use App\Repository\ActivityRepository;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
final class ActivityByIdType extends AbstractType
|
||||||
|
{
|
||||||
|
public function __construct(private readonly ActivityRepository $repository)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder->addModelTransformer(new EntityByIdTransformer($this->repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): string
|
||||||
|
{
|
||||||
|
return ActivityType::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/Form/Type/CustomerByIdType.php
Normal file
32
src/Form/Type/CustomerByIdType.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?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\Form\Type;
|
||||||
|
|
||||||
|
use App\Form\DataTransformer\EntityByIdTransformer;
|
||||||
|
use App\Repository\CustomerRepository;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
final class CustomerByIdType extends AbstractType
|
||||||
|
{
|
||||||
|
public function __construct(private readonly CustomerRepository $repository)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder->addModelTransformer(new EntityByIdTransformer($this->repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): string
|
||||||
|
{
|
||||||
|
return CustomerType::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/Form/Type/ProjectByIdType.php
Normal file
32
src/Form/Type/ProjectByIdType.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?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\Form\Type;
|
||||||
|
|
||||||
|
use App\Form\DataTransformer\EntityByIdTransformer;
|
||||||
|
use App\Repository\ProjectRepository;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
final class ProjectByIdType extends AbstractType
|
||||||
|
{
|
||||||
|
public function __construct(private readonly ProjectRepository $repository)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder->addModelTransformer(new EntityByIdTransformer($this->repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): string
|
||||||
|
{
|
||||||
|
return ProjectType::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/Form/Type/TeamByIdType.php
Normal file
32
src/Form/Type/TeamByIdType.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?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\Form\Type;
|
||||||
|
|
||||||
|
use App\Form\DataTransformer\EntityByIdTransformer;
|
||||||
|
use App\Repository\TeamRepository;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
final class TeamByIdType extends AbstractType
|
||||||
|
{
|
||||||
|
public function __construct(private readonly TeamRepository $repository)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder->addModelTransformer(new EntityByIdTransformer($this->repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): string
|
||||||
|
{
|
||||||
|
return TeamType::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/Form/Type/UserByIdType.php
Normal file
32
src/Form/Type/UserByIdType.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?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\Form\Type;
|
||||||
|
|
||||||
|
use App\Form\DataTransformer\EntityByIdTransformer;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
final class UserByIdType extends AbstractType
|
||||||
|
{
|
||||||
|
public function __construct(private readonly UserRepository $repository)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder->addModelTransformer(new EntityByIdTransformer($this->repository));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): string
|
||||||
|
{
|
||||||
|
return UserType::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
namespace App\WorkingTime\Mode;
|
namespace App\WorkingTime\Mode;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
|
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
|
||||||
|
|
||||||
final class WorkingTimeModeFactory
|
final class WorkingTimeModeFactory
|
||||||
@@ -19,7 +20,8 @@ final class WorkingTimeModeFactory
|
|||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[TaggedIterator(WorkingTimeMode::class)]
|
#[TaggedIterator(WorkingTimeMode::class)]
|
||||||
private readonly iterable $modes
|
private readonly iterable $modes,
|
||||||
|
private readonly LoggerInterface $logger
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -39,7 +41,15 @@ final class WorkingTimeModeFactory
|
|||||||
|
|
||||||
public function getModeForUser(User $user): WorkingTimeMode
|
public function getModeForUser(User $user): WorkingTimeMode
|
||||||
{
|
{
|
||||||
return $this->getMode($user->getWorkContractMode());
|
try {
|
||||||
|
return $this->getMode($user->getWorkContractMode());
|
||||||
|
} catch (\InvalidArgumentException $ex) {
|
||||||
|
$this->logger->error(
|
||||||
|
\sprintf('Unknown mode "%s" requested for user %s', $user->getWorkContractMode(), $user->getId())
|
||||||
|
);
|
||||||
|
|
||||||
|
return new WorkingTimeModeNone(); // @CloudRequired
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMode(string $contractMode): WorkingTimeMode
|
public function getMode(string $contractMode): WorkingTimeMode
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace App\WorkingTime;
|
|||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Entity\WorkingTime;
|
use App\Entity\WorkingTime;
|
||||||
use App\Event\WorkingTimeApproveMonthEvent;
|
use App\Event\WorkingTimeApproveMonthEvent;
|
||||||
|
use App\Event\WorkingTimeQueryStatsEvent;
|
||||||
use App\Event\WorkingTimeYearEvent;
|
use App\Event\WorkingTimeYearEvent;
|
||||||
use App\Event\WorkingTimeYearSummaryEvent;
|
use App\Event\WorkingTimeYearSummaryEvent;
|
||||||
use App\Repository\TimesheetRepository;
|
use App\Repository\TimesheetRepository;
|
||||||
@@ -235,6 +236,9 @@ final class WorkingTimeService
|
|||||||
->addGroupBy('day')
|
->addGroupBy('day')
|
||||||
;
|
;
|
||||||
|
|
||||||
|
$event = new WorkingTimeQueryStatsEvent($qb, $user, $begin, $end);
|
||||||
|
$this->eventDispatcher->dispatch($event);
|
||||||
|
|
||||||
$results = $qb->getQuery()->getResult();
|
$results = $qb->getQuery()->getResult();
|
||||||
|
|
||||||
$durations = [];
|
$durations = [];
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class ActionsControllerTest extends APIControllerBaseTestCase
|
|||||||
$this->assertUrlIsSecured('/api/actions/timesheet/1/index/en');
|
$this->assertUrlIsSecured('/api/actions/timesheet/1/index/en');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_getTimesheetActions(): void
|
public function testGetTimesheetActions(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class ActionsControllerTest extends APIControllerBaseTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_getActivityActions(): void
|
public function testGetActivityActions(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ class ActionsControllerTest extends APIControllerBaseTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_getProjectActions(): void
|
public function testGetProjectActions(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ class ActionsControllerTest extends APIControllerBaseTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_getCustomerActions(): void
|
public function testGetCustomerActions(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||||
|
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ class LocaleFormatExtensionsTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @dataProvider getMoneyData62_1
|
* @dataProvider getMoneyData62_1
|
||||||
*/
|
*/
|
||||||
public function testMoney62_1(string $result, null|int|float $amount, string $currency, string $locale): void
|
public function testMoney621(string $result, null|int|float $amount, string $currency, string $locale): void
|
||||||
{
|
{
|
||||||
IntlTestHelper::requireFullIntl($this, '62.1');
|
IntlTestHelper::requireFullIntl($this, '62.1');
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ use App\WorkingTime\Mode\WorkingTimeModeDay;
|
|||||||
use App\WorkingTime\Mode\WorkingTimeModeFactory;
|
use App\WorkingTime\Mode\WorkingTimeModeFactory;
|
||||||
use App\WorkingTime\Mode\WorkingTimeModeNone;
|
use App\WorkingTime\Mode\WorkingTimeModeNone;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\WorkingTime\Mode\WorkingTimeModeFactory
|
* @covers \App\WorkingTime\Mode\WorkingTimeModeFactory
|
||||||
@@ -25,7 +27,7 @@ class WorkingTimeModeFactoryTest extends TestCase
|
|||||||
$none = new WorkingTimeModeNone();
|
$none = new WorkingTimeModeNone();
|
||||||
$day = new WorkingTimeModeDay();
|
$day = new WorkingTimeModeDay();
|
||||||
$modes = [$none, $day];
|
$modes = [$none, $day];
|
||||||
$sut = new WorkingTimeModeFactory($modes);
|
$sut = new WorkingTimeModeFactory($modes, new NullLogger());
|
||||||
self::assertEquals($modes, $sut->getAll());
|
self::assertEquals($modes, $sut->getAll());
|
||||||
self::assertSame($none, $sut->getMode('none'));
|
self::assertSame($none, $sut->getMode('none'));
|
||||||
self::assertSame($day, $sut->getMode('day'));
|
self::assertSame($day, $sut->getMode('day'));
|
||||||
@@ -35,12 +37,24 @@ class WorkingTimeModeFactoryTest extends TestCase
|
|||||||
self::assertSame($day, $sut->getModeForUser($user));
|
self::assertSame($day, $sut->getModeForUser($user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFallbackMode(): void
|
||||||
|
{
|
||||||
|
$logger = $this->createMock(LoggerInterface::class);
|
||||||
|
$logger->expects(self::once())->method('error');
|
||||||
|
$modes = [new WorkingTimeModeNone(), new WorkingTimeModeDay()];
|
||||||
|
$sut = new WorkingTimeModeFactory($modes, $logger);
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$user->setWorkContractMode('foo');
|
||||||
|
self::assertInstanceOf(WorkingTimeModeNone::class, $sut->getModeForUser($user));
|
||||||
|
}
|
||||||
|
|
||||||
public function testException(): void
|
public function testException(): void
|
||||||
{
|
{
|
||||||
$this->expectException(\InvalidArgumentException::class);
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
$this->expectExceptionMessage('Unknown working contract mode: foo');
|
$this->expectExceptionMessage('Unknown working contract mode: foo');
|
||||||
|
|
||||||
$sut = new WorkingTimeModeFactory([]);
|
$sut = new WorkingTimeModeFactory([], new NullLogger());
|
||||||
$sut->getMode('foo');
|
$sut->getMode('foo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,18 @@
|
|||||||
<source>daterangepicker.allTime</source>
|
<source>daterangepicker.allTime</source>
|
||||||
<target>Gesamter Zeitraum</target>
|
<target>Gesamter Zeitraum</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="8f61k.L" resname="daterangepicker.thisMonth">
|
||||||
|
<source>daterangepicker.thisMonth</source>
|
||||||
|
<target>Dieser Monat</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="AdEC6w4" resname="daterangepicker.lastMonth">
|
||||||
|
<source>daterangepicker.lastMonth</source>
|
||||||
|
<target>Letzter Monat</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="mgJ2Z7M" resname="daterangepicker.thisFinancialYear">
|
||||||
|
<source>daterangepicker.thisFinancialYear</source>
|
||||||
|
<target>Dieses Geschäftsjahr</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -34,6 +34,18 @@
|
|||||||
<source>daterangepicker.allTime</source>
|
<source>daterangepicker.allTime</source>
|
||||||
<target>Total period</target>
|
<target>Total period</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="8f61k.L" resname="daterangepicker.thisMonth">
|
||||||
|
<source>daterangepicker.thisMonth</source>
|
||||||
|
<target>This month</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="AdEC6w4" resname="daterangepicker.lastMonth">
|
||||||
|
<source>daterangepicker.lastMonth</source>
|
||||||
|
<target>Last month</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="mgJ2Z7M" resname="daterangepicker.thisFinancialYear">
|
||||||
|
<source>daterangepicker.thisFinancialYear</source>
|
||||||
|
<target>This financial year</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -1750,6 +1750,10 @@
|
|||||||
<source>booking_allow_only_work_days</source>
|
<source>booking_allow_only_work_days</source>
|
||||||
<target>Erlaube Zeiteinträge nur an Tagen, für die im Arbeitsvertrag Sollstunden hinterlegt sind</target>
|
<target>Erlaube Zeiteinträge nur an Tagen, für die im Arbeitsvertrag Sollstunden hinterlegt sind</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="HQ2YM1b" resname="attendance_only_project">
|
||||||
|
<source>attendance_only_project</source>
|
||||||
|
<target>Nur Einträge des ausgewählten Projekts als Arbeitszeit zählen</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="q4ooRfF" resname="expires">
|
<trans-unit id="q4ooRfF" resname="expires">
|
||||||
<source>Expiry date</source>
|
<source>Expiry date</source>
|
||||||
<target>Ablaufdatum</target>
|
<target>Ablaufdatum</target>
|
||||||
|
|||||||
@@ -1750,6 +1750,10 @@
|
|||||||
<source>booking_allow_only_work_days</source>
|
<source>booking_allow_only_work_days</source>
|
||||||
<target>Allow time entries only for days for which expected hours are defined in the employment contract</target>
|
<target>Allow time entries only for days for which expected hours are defined in the employment contract</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="HQ2YM1b" resname="attendance_only_project">
|
||||||
|
<source>attendance_only_project</source>
|
||||||
|
<target>Only count entries of the selected project as working time</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="q4ooRfF" resname="expires">
|
<trans-unit id="q4ooRfF" resname="expires">
|
||||||
<source>Expiry date</source>
|
<source>Expiry date</source>
|
||||||
<target>Expiry date</target>
|
<target>Expiry date</target>
|
||||||
|
|||||||
Reference in New Issue
Block a user