From 7460647d58b6bb47d14bae5e6342bd3905ef6052 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Sat, 12 Jun 2021 01:05:33 +0200 Subject: [PATCH] added setting to limit the maximum length of a timesheet record (#2612) --- src/Configuration/SystemConfiguration.php | 15 +++ .../SystemConfigurationController.php | 16 +++ src/DependencyInjection/Configuration.php | 9 ++ .../Constraints/TimesheetLongRunning.php | 26 ++++ .../TimesheetLongRunningValidator.php | 69 +++++++++++ templates/base.html.twig | 2 +- templates/user/stats.html.twig | 4 + tests/API/APIControllerBaseTest.php | 18 ++- tests/API/TimesheetControllerTest.php | 44 +++++-- tests/Controller/TimesheetControllerTest.php | 24 ++-- .../TimesheetTeamControllerTest.php | 7 +- tests/DataFixtures/TimesheetFixtures.php | 15 +++ .../DependencyInjection/AppExtensionTest.php | 2 + .../DependencyInjection/ConfigurationTest.php | 2 + .../TimesheetLongRunningValidatorTest.php | 117 ++++++++++++++++++ translations/system-configuration.de.xlf | 8 ++ translations/system-configuration.en.xlf | 8 ++ translations/validators.de.xlf | 4 + translations/validators.en.xlf | 4 + 19 files changed, 366 insertions(+), 28 deletions(-) create mode 100644 src/Validator/Constraints/TimesheetLongRunning.php create mode 100644 src/Validator/Constraints/TimesheetLongRunningValidator.php create mode 100644 tests/Validator/Constraints/TimesheetLongRunningValidatorTest.php diff --git a/src/Configuration/SystemConfiguration.php b/src/Configuration/SystemConfiguration.php index cd5eda90..cfa36239 100644 --- a/src/Configuration/SystemConfiguration.php +++ b/src/Configuration/SystemConfiguration.php @@ -211,6 +211,16 @@ class SystemConfiguration implements SystemBundleConfiguration } // ========== Timesheet configurations ========== + /* + public function getTimesheetBreakWarningDuration(): int + { + return (int) $this->find('timesheet.rules.break_warning_duration'); + } + */ + public function getTimesheetLongRunningDuration(): int + { + return (int) $this->find('timesheet.rules.long_running_duration'); + } public function getTimesheetDefaultBeginTime(): string { @@ -352,6 +362,11 @@ class SystemConfiguration implements SystemBundleConfiguration return (bool) $this->find('theme.colors_limited'); } + public function getThemeAutocompleteCharacters(): int + { + return (int) $this->find('theme.autocomplete_chars'); + } + public function getThemeColorChoices(): ?array { $config = $this->find('theme.color_choices'); diff --git a/src/Controller/SystemConfigurationController.php b/src/Controller/SystemConfigurationController.php index 23cd2c27..df12da95 100644 --- a/src/Controller/SystemConfigurationController.php +++ b/src/Controller/SystemConfigurationController.php @@ -365,6 +365,22 @@ final class SystemConfigurationController extends AbstractController ->setConstraints([ new GreaterThanOrEqual(['value' => 0]) ]), + /* + (new Configuration()) + ->setName('timesheet.rules.break_warning_duration') + ->setType(IntegerType::class) + ->setTranslationDomain('system-configuration') + ->setConstraints([ + new GreaterThanOrEqual(['value' => 0]) + ]), + */ + (new Configuration()) + ->setName('timesheet.rules.long_running_duration') + ->setType(IntegerType::class) + ->setTranslationDomain('system-configuration') + ->setConstraints([ + new GreaterThanOrEqual(['value' => 0]) + ]), ]), (new SystemConfigurationModel()) ->setSection(SystemConfigurationModel::SECTION_LOCKDOWN) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 6f5088f3..9cb98574 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -242,6 +242,15 @@ class Configuration implements ConfigurationInterface ->scalarNode('lockdown_grace_period') ->defaultNull() ->end() + ->scalarNode('lockdown_grace_period') + ->defaultNull() + ->end() + ->integerNode('break_warning_duration') + ->defaultValue(0) + ->end() + ->integerNode('long_running_duration') + ->defaultValue(0) + ->end() ->end() ->end() ->end() diff --git a/src/Validator/Constraints/TimesheetLongRunning.php b/src/Validator/Constraints/TimesheetLongRunning.php new file mode 100644 index 00000000..9694875c --- /dev/null +++ b/src/Validator/Constraints/TimesheetLongRunning.php @@ -0,0 +1,26 @@ + 'TIMESHEET_LONG_RUNNING', + ]; + + public $message = 'Maximum duration of {{ value }} hours exceeded.'; + + public function getTargets() + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/src/Validator/Constraints/TimesheetLongRunningValidator.php b/src/Validator/Constraints/TimesheetLongRunningValidator.php new file mode 100644 index 00000000..2c586d0e --- /dev/null +++ b/src/Validator/Constraints/TimesheetLongRunningValidator.php @@ -0,0 +1,69 @@ +systemConfiguration = $systemConfiguration; + } + + /** + * @param TimesheetEntity $timesheet + * @param Constraint $constraint + */ + public function validate($timesheet, Constraint $constraint) + { + if (!($constraint instanceof TimesheetLongRunning)) { + throw new UnexpectedTypeException($constraint, TimesheetLongRunning::class); + } + + if (!\is_object($timesheet) || !($timesheet instanceof TimesheetEntity)) { + throw new UnexpectedTypeException($timesheet, TimesheetEntity::class); + } + + if ($timesheet->isRunning()) { + return; + } + + $maxMinutes = $this->systemConfiguration->getTimesheetLongRunningDuration(); + + if ($maxMinutes <= 0) { + return; + } + + $duration = $timesheet->getEnd()->getTimestamp() - $timesheet->getBegin()->getTimestamp(); + $minutes = (int) $duration / 60; + + if ($minutes < $maxMinutes) { + return; + } + + $format = new \App\Utils\Duration(); + $hours = $format->format($maxMinutes * 60); + + // raise a violation for all entries before the start of lockdown period + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $hours) + ->setTranslationDomain('validators') + ->atPath('duration') + ->setCode(TimesheetLongRunning::LONG_RUNNING) + ->addViolation(); + } +} diff --git a/templates/base.html.twig b/templates/base.html.twig index c249a685..7d425f3c 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -275,7 +275,7 @@ locale: '{{ app.request.locale }}', first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }}, twentyFourHours: {{ 'true'|hour24('false') }}, - autoComplete: {{ theme_config('autocomplete_chars') }}, + autoComplete: {{ kimai_config.themeAutocompleteCharacters }}, defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}', updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %} }, diff --git a/templates/user/stats.html.twig b/templates/user/stats.html.twig index b0d49f8f..2470139b 100644 --- a/templates/user/stats.html.twig +++ b/templates/user/stats.html.twig @@ -177,6 +177,10 @@ {{ 'profile.about_me'|trans }} {% endblock %} {% block box_body %} +

+ {{ 'label.username'|trans }}
+ {{ user.username }} +

{{ 'profile.first_entry'|trans }}
{# FIXME use a configuration for it #} diff --git a/tests/API/APIControllerBaseTest.php b/tests/API/APIControllerBaseTest.php index cb7fc1f1..66459e2e 100644 --- a/tests/API/APIControllerBaseTest.php +++ b/tests/API/APIControllerBaseTest.php @@ -217,7 +217,7 @@ abstract class APIControllerBaseTest extends ControllerBaseTest /** * @param Response $response - * @param string[] $failedFields + * @param array|array $failedFields * @param bool $extraFields test for the error "This form should not contain extra fields" */ protected function assertApiCallValidationError(Response $response, array $failedFields, bool $extraFields = false) @@ -235,9 +235,21 @@ abstract class APIControllerBaseTest extends ControllerBaseTest self::assertArrayHasKey('children', $result['errors']); $data = $result['errors']['children']; - foreach ($failedFields as $fieldName) { - self::assertArrayHasKey($fieldName, $data, sprintf('Could not find validation error for field: %s', $fieldName)); + foreach ($failedFields as $key => $value) { + $messages = []; + $fieldName = $value; + if (\is_string($key)) { + $fieldName = $key; + $messages = $value; + if (!\is_array($messages)) { + $messages = [$value]; + } + } + self::assertArrayHasKey($fieldName, $data, sprintf('Could not find validation error for field "%s" in list: %s', $fieldName, implode(', ', $failedFields))); self::assertArrayHasKey('errors', $data[$fieldName], sprintf('Field %s has no validation problem', $fieldName)); + foreach ($messages as $i => $message) { + self::assertEquals($message, $data[$fieldName]['errors'][$i]); + } } $foundErrors = []; diff --git a/tests/API/TimesheetControllerTest.php b/tests/API/TimesheetControllerTest.php index d254c10e..d00646b1 100644 --- a/tests/API/TimesheetControllerTest.php +++ b/tests/API/TimesheetControllerTest.php @@ -389,7 +389,7 @@ class TimesheetControllerTest extends APIControllerBaseTest $data = [ 'activity' => 1, 'project' => 1, - 'begin' => ($dateTime->createDateTime('- 16 hours'))->format('Y-m-d H:m:0'), + 'begin' => ($dateTime->createDateTime('-8 hours'))->format('Y-m-d H:m:0'), 'end' => ($dateTime->createDateTime())->format('Y-m-d H:m:0'), 'description' => 'foo', 'fixedRate' => 2016, @@ -402,7 +402,7 @@ class TimesheetControllerTest extends APIControllerBaseTest $this->assertIsArray($result); self::assertApiResponseTypeStructure('TimesheetEntity', $result); $this->assertNotEmpty($result['id']); - $this->assertTrue($result['duration'] == 57600 || $result['duration'] == 57660); // 1 minute rounding might be applied + $this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied $this->assertEquals(2016, $result['rate']); } @@ -413,7 +413,7 @@ class TimesheetControllerTest extends APIControllerBaseTest $data = [ 'activity' => 1, 'project' => 1, - 'begin' => ($dateTime->createDateTime('- 16 hours'))->format('Y-m-d H:m:0'), + 'begin' => ($dateTime->createDateTime('-8 hours'))->format('Y-m-d H:m:0'), 'end' => ($dateTime->createDateTime())->format('Y-m-d H:m:0'), 'description' => 'foo', 'fixedRate' => 2016, @@ -426,7 +426,7 @@ class TimesheetControllerTest extends APIControllerBaseTest $this->assertIsArray($result); self::assertApiResponseTypeStructure('TimesheetEntityFull', $result); $this->assertNotEmpty($result['id']); - $this->assertTrue($result['duration'] == 57600 || $result['duration'] == 57660); // 1 minute rounding might be applied + $this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied $this->assertEquals(2016, $result['rate']); } @@ -443,7 +443,7 @@ class TimesheetControllerTest extends APIControllerBaseTest 'activity' => 1, 'project' => 1, 'user' => $user->getId(), - 'begin' => ($dateTime->createDateTime('- 16 hours'))->format('Y-m-d H:m:0'), + 'begin' => ($dateTime->createDateTime('- 8 hours'))->format('Y-m-d H:m:0'), 'end' => ($dateTime->createDateTime())->format('Y-m-d H:m:0'), 'description' => 'foo', 'fixedRate' => 2016, @@ -744,7 +744,7 @@ class TimesheetControllerTest extends APIControllerBaseTest $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); $this->importFixtureForUser(User::ROLE_USER); - $start = new \DateTime('-10 days'); + $start = new \DateTime('-8 hours'); $fixture = new TimesheetFixtures(); $fixture @@ -752,7 +752,7 @@ class TimesheetControllerTest extends APIControllerBaseTest ->setHourlyRate(true) ->setAmount(0) ->setUser($this->getUserByRole(User::ROLE_USER)) - ->setStartDate($start) + ->setFixedStartDate($start) ->setAmountRunning(1) ; $timesheets = $this->importFixture($fixture); @@ -772,6 +772,30 @@ class TimesheetControllerTest extends APIControllerBaseTest $this->assertInstanceOf(\DateTime::class, $timesheet->getEnd()); } + public function testStopActionTriggersValidationOnLongRunning() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + $this->setSystemConfiguration('timesheet.rules.long_running_duration', 750); + $this->importFixtureForUser(User::ROLE_USER); + + $start = new \DateTime('-13 hours'); + + $fixture = new TimesheetFixtures(); + $fixture + ->setFixedRate(true) + ->setHourlyRate(true) + ->setAmount(0) + ->setUser($this->getUserByRole(User::ROLE_USER)) + ->setFixedStartDate($start) + ->setAmountRunning(1) + ; + $timesheets = $this->importFixture($fixture); + $id = $timesheets[0]->getId(); + + $this->request($client, '/api/timesheets/' . $id . '/stop', 'PATCH'); + $this->assertApiCallValidationError($client->getResponse(), ['duration' => 'Maximum 12:30 hours allowed.']); + } + public function testStopActionFailsOnStoppedEntry() { $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); @@ -995,7 +1019,7 @@ class TimesheetControllerTest extends APIControllerBaseTest $data = [ 'activity' => 1, 'project' => 1, - 'begin' => ($dateTime->createDateTime('- 16 hours'))->format('Y-m-d H:m:0'), + 'begin' => ($dateTime->createDateTime('- 8 hours'))->format('Y-m-d H:m:0'), 'end' => ($dateTime->createDateTime())->format('Y-m-d H:m:0'), 'description' => 'foo', 'fixedRate' => 2016, @@ -1008,7 +1032,7 @@ class TimesheetControllerTest extends APIControllerBaseTest $this->assertIsArray($result); self::assertApiResponseTypeStructure('TimesheetEntity', $result); $this->assertNotEmpty($result['id']); - $this->assertTrue($result['duration'] == 57600 || $result['duration'] == 57660); // 1 minute rounding might be applied + $this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied $this->assertEquals(2016, $result['rate']); $this->request($client, '/api/timesheets/' . $result['id'] . '/duplicate', 'PATCH'); @@ -1018,7 +1042,7 @@ class TimesheetControllerTest extends APIControllerBaseTest $this->assertIsArray($result); self::assertApiResponseTypeStructure('TimesheetEntity', $result); $this->assertNotEmpty($result['id']); - $this->assertTrue($result['duration'] == 57600 || $result['duration'] == 57660); // 1 minute rounding might be applied + $this->assertTrue($result['duration'] == 28800 || $result['duration'] == 28860); // 1 minute rounding might be applied $this->assertEquals(2016, $result['rate']); } diff --git a/tests/Controller/TimesheetControllerTest.php b/tests/Controller/TimesheetControllerTest.php index 0ec8c0e8..ca83126c 100644 --- a/tests/Controller/TimesheetControllerTest.php +++ b/tests/Controller/TimesheetControllerTest.php @@ -304,10 +304,10 @@ class TimesheetControllerTest extends ControllerBaseTest $this->assertEquals(50, $timesheet->getRate()); $expected = new \DateTime('2018-08-02T20:00:00'); - $this->assertEquals($expected->format(\DateTime::ATOM), $timesheet->getBegin()->format(\DateTime::ATOM)); + $this->assertEquals($expected->format(\DateTimeInterface::ATOM), $timesheet->getBegin()->format(\DateTimeInterface::ATOM)); $expected = new \DateTime('2018-08-02T20:30:00'); - $this->assertEquals($expected->format(\DateTime::ATOM), $timesheet->getEnd()->format(\DateTime::ATOM)); + $this->assertEquals($expected->format(\DateTimeInterface::ATOM), $timesheet->getEnd()->format(\DateTimeInterface::ATOM)); } public function testCreateActionWithFromAndToValuesTwice() @@ -338,10 +338,10 @@ class TimesheetControllerTest extends ControllerBaseTest $this->assertEquals(50, $timesheet->getRate()); $expected = new \DateTime('2018-08-02T20:00:00'); - $this->assertEquals($expected->format(\DateTime::ATOM), $timesheet->getBegin()->format(\DateTime::ATOM)); + $this->assertEquals($expected->format(\DateTimeInterface::ATOM), $timesheet->getBegin()->format(\DateTimeInterface::ATOM)); $expected = new \DateTime('2018-08-02T20:30:00'); - $this->assertEquals($expected->format(\DateTime::ATOM), $timesheet->getEnd()->format(\DateTime::ATOM)); + $this->assertEquals($expected->format(\DateTimeInterface::ATOM), $timesheet->getEnd()->format(\DateTimeInterface::ATOM)); // create a second entry that is overlapping $this->request($client, '/timesheet/create?from=2018-08-02T20%3A02%3A00&to=2018-08-02T20%3A20%3A00'); @@ -489,10 +489,10 @@ class TimesheetControllerTest extends ControllerBaseTest $this->assertEquals(800, $timesheet->getRate()); $expected = new \DateTime('2018-08-02T10:00:00'); - $this->assertEquals($expected->format(\DateTime::ATOM), $timesheet->getBegin()->format(\DateTime::ATOM)); + $this->assertEquals($expected->format(\DateTimeInterface::ATOM), $timesheet->getBegin()->format(\DateTimeInterface::ATOM)); $expected = new \DateTime('2018-08-02T18:00:00'); - $this->assertEquals($expected->format(\DateTime::ATOM), $timesheet->getEnd()->format(\DateTime::ATOM)); + $this->assertEquals($expected->format(\DateTimeInterface::ATOM), $timesheet->getEnd()->format(\DateTimeInterface::ATOM)); $this->assertEquals(['one', 'two', 'three'], $timesheet->getTagsAsArray()); } @@ -502,9 +502,9 @@ class TimesheetControllerTest extends ControllerBaseTest $client = $this->getClientForAuthenticatedUser(); $fixture = new TimesheetFixtures(); - $fixture->setAmount(10); + $fixture->setAmount(1); $fixture->setUser($this->getUserByRole(User::ROLE_USER)); - $fixture->setStartDate('2017-05-01'); + $fixture->setFixedStartDate(new \DateTime('-2 hours')); $timesheets = $this->importFixture($fixture); $id = $timesheets[0]->getId(); @@ -527,6 +527,10 @@ class TimesheetControllerTest extends ControllerBaseTest ] ]); + if (!$client->getResponse()->isRedirect()) { + dd($response->getStatusCode(), $response->getContent()); + } + $this->assertIsRedirect($client, $this->createUrl('/timesheet/')); $client->followRedirect(); $this->assertTrue($client->getResponse()->isSuccessful()); @@ -645,7 +649,7 @@ class TimesheetControllerTest extends ControllerBaseTest $fixture->setCallback(function (Timesheet $timesheet) { $timesheet->setDescription('Testing is fun!'); $end = clone $timesheet->getBegin(); - $end->modify('+ 16 hours'); + $end->modify('+ 8 hours'); $timesheet->setEnd($end); $timesheet->setFixedRate(2016); $timesheet->setHourlyRate(127); @@ -675,7 +679,7 @@ class TimesheetControllerTest extends ControllerBaseTest $this->assertEquals(2016, $timesheet->getRate()); $this->assertEquals(127, $timesheet->getHourlyRate()); $this->assertEquals(2016, $timesheet->getFixedRate()); - $this->assertTrue($timesheet->getDuration() == 57600 || $timesheet->getDuration() == 57660); // 1 minute rounding might be applied + $this->assertTrue($timesheet->getDuration() == 28800 || $timesheet->getDuration() == 28860); // 1 minute rounding might be applied $this->assertEquals(2016, $timesheet->getRate()); } } diff --git a/tests/Controller/TimesheetTeamControllerTest.php b/tests/Controller/TimesheetTeamControllerTest.php index f30af226..c7f00848 100644 --- a/tests/Controller/TimesheetTeamControllerTest.php +++ b/tests/Controller/TimesheetTeamControllerTest.php @@ -265,13 +265,12 @@ class TimesheetTeamControllerTest extends ControllerBaseTest { $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); - $em = $this->getEntityManager(); $user = $this->getUserByRole(User::ROLE_USER); $teamlead = $this->getUserByRole(User::ROLE_TEAMLEAD); $fixture = new TimesheetFixtures(); $fixture->setAmount(10); $fixture->setUser($user); - $fixture->setStartDate('2017-05-01'); + $fixture->setFixedStartDate(new \DateTime('-2 hours')); $timesheets = $this->importFixture($fixture); $id = $timesheets[0]->getId(); @@ -420,7 +419,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest $fixture->setCallback(function (Timesheet $timesheet) { $timesheet->setDescription('Testing is fun!'); $end = clone $timesheet->getBegin(); - $end->modify('+ 16 hours'); + $end->modify('+ 8 hours'); $timesheet->setEnd($end); $timesheet->setFixedRate(2016); $timesheet->setHourlyRate(127); @@ -450,7 +449,7 @@ class TimesheetTeamControllerTest extends ControllerBaseTest $this->assertEquals(2016, $timesheet->getRate()); $this->assertEquals(127, $timesheet->getHourlyRate()); $this->assertEquals(2016, $timesheet->getFixedRate()); - $this->assertTrue($timesheet->getDuration() == 57600 || $timesheet->getDuration() == 57660); // 1 minute rounding might be applied + $this->assertTrue($timesheet->getDuration() == 28800 || $timesheet->getDuration() == 28860); // 1 minute rounding might be applied $this->assertEquals(2016, $timesheet->getRate()); } } diff --git a/tests/DataFixtures/TimesheetFixtures.php b/tests/DataFixtures/TimesheetFixtures.php index c26b4edc..1fe6c566 100644 --- a/tests/DataFixtures/TimesheetFixtures.php +++ b/tests/DataFixtures/TimesheetFixtures.php @@ -48,6 +48,10 @@ final class TimesheetFixtures implements TestFixture * @var \DateTime */ private $startDate; + /** + * @var \DateTime + */ + private $fixedStartDate; /** * @var bool */ @@ -129,6 +133,13 @@ final class TimesheetFixtures implements TestFixture return $this; } + public function setFixedStartDate(\DateTime $date): TimesheetFixtures + { + $this->fixedStartDate = $date; + + return $this; + } + public function setAmountRunning(int $amount): TimesheetFixtures { $this->running = $amount; @@ -312,6 +323,10 @@ final class TimesheetFixtures implements TestFixture private function getDateTime(int $i): \DateTime { + if ($this->fixedStartDate !== null) { + return $this->fixedStartDate; + } + if ($this->startDate === null) { $this->startDate = new \DateTime('2018-04-01'); } diff --git a/tests/DependencyInjection/AppExtensionTest.php b/tests/DependencyInjection/AppExtensionTest.php index f7318e14..2dc22b99 100644 --- a/tests/DependencyInjection/AppExtensionTest.php +++ b/tests/DependencyInjection/AppExtensionTest.php @@ -204,6 +204,8 @@ class AppExtensionTest extends TestCase 'lockdown_grace_period' => null, 'allow_overbooking_budget' => true, 'lockdown_period_timezone' => null, + 'break_warning_duration' => 0, + 'long_running_duration' => 0, ], 'default_begin' => 'now', 'duration_increment' => null, diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index 5c16775f..4c40d832 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -289,6 +289,8 @@ class ConfigurationTest extends TestCase 'lockdown_grace_period' => null, 'allow_overbooking_budget' => true, 'lockdown_period_timezone' => null, + 'break_warning_duration' => 0, + 'long_running_duration' => 0, ], 'duration_increment' => null, 'time_increment' => null, diff --git a/tests/Validator/Constraints/TimesheetLongRunningValidatorTest.php b/tests/Validator/Constraints/TimesheetLongRunningValidatorTest.php new file mode 100644 index 00000000..4d18cf20 --- /dev/null +++ b/tests/Validator/Constraints/TimesheetLongRunningValidatorTest.php @@ -0,0 +1,117 @@ +createMyValidator(120); + } + + protected function createMyValidator(int $minutes) + { + $loader = $this->createMock(ConfigLoaderInterface::class); + $config = new SystemConfiguration($loader, [ + 'timesheet' => [ + 'rules' => [ + 'long_running_duration' => $minutes, + ], + ], + ]); + + return new TimesheetLongRunningValidator($config); + } + + public function testConstraintIsInvalid() + { + $this->expectException(UnexpectedTypeException::class); + + $this->validator->validate(new Timesheet(), new NotBlank()); + } + + public function testInvalidValueThrowsException() + { + $this->expectException(UnexpectedTypeException::class); + + $this->validator->validate(new NotBlank(), new TimesheetLongRunning(['message' => 'myMessage'])); + } + + public function testLongRunningTriggers() + { + $begin = new \DateTime(); + $end = new \DateTime('+10 hour'); + $timesheet = new Timesheet(); + $timesheet->setBegin($begin); + $timesheet->setEnd($end); + + $this->validator->validate($timesheet, new TimesheetLongRunning()); + + $this->buildViolation('Maximum duration of {{ value }} hours exceeded.') + ->atPath('property.path.duration') + ->setParameter('{{ value }}', '02:00') + ->setCode(TimesheetLongRunning::LONG_RUNNING) + ->assertRaised(); + } + + public function testLongRunningNotTriggersIfConfiguredToZero() + { + $this->validator = $this->createMyValidator(0); + $this->validator->initialize($this->context); + + $begin = new \DateTime(); + $end = new \DateTime('+10 hour'); + $timesheet = new Timesheet(); + $timesheet->setBegin($begin); + $timesheet->setEnd($end); + + $this->validator->validate($timesheet, new TimesheetLongRunning()); + + $this->assertNoViolation(); + } + + public function testLongRunningNotTriggersIfDurationIsLowerThan() + { + $this->validator = $this->createMyValidator(121); + $this->validator->initialize($this->context); + + $begin = new \DateTime(); + $end = new \DateTime('+2 hour'); + $timesheet = new Timesheet(); + $timesheet->setBegin($begin); + $timesheet->setEnd($end); + + $this->validator->validate($timesheet, new TimesheetLongRunning()); + + $this->assertNoViolation(); + } + + public function testNotTriggersOnRunningRecord() + { + $begin = new \DateTime('-10 hour'); + $timesheet = new Timesheet(); + $timesheet->setBegin($begin); + + $this->validator->validate($timesheet, new TimesheetLongRunning()); + $this->assertNoViolation(); + } +} diff --git a/translations/system-configuration.de.xlf b/translations/system-configuration.de.xlf index 196f0227..545a333e 100644 --- a/translations/system-configuration.de.xlf +++ b/translations/system-configuration.de.xlf @@ -298,6 +298,14 @@ label.saml_activate SAML Anmeldung + + label.timesheet.rules.break_warning_duration + Maximale Dauer eines Zeiteintrags in Minuten, bevor eine Pausen-Warnung angezeigt wird (0 = deaktiviert) + + + label.timesheet.rules.long_running_duration + Maximale Dauer eines Zeiteintrags in Minuten, bevor das Speichern abgelehnt wird (0 = deaktiviert) + diff --git a/translations/system-configuration.en.xlf b/translations/system-configuration.en.xlf index a2304f4a..1b37eae3 100644 --- a/translations/system-configuration.en.xlf +++ b/translations/system-configuration.en.xlf @@ -298,6 +298,14 @@ label.saml_activate SAML login + + label.timesheet.rules.break_warning_duration + Maximum duration of a timesheet record in minutes before a "break warning" will be displayed (0 = deactivated) + + + label.timesheet.rules.long_running_duration + Maximum duration of a timesheet record in minutes before saving is rejected (0 = deactivated) + diff --git a/translations/validators.de.xlf b/translations/validators.de.xlf index 561cd1b1..04d048cf 100644 --- a/translations/validators.de.xlf +++ b/translations/validators.de.xlf @@ -50,6 +50,10 @@ The budget is completely used. Das Budget ist aufgebraucht. Von den vorhandenen %budget% wurden bisher %used% gebucht, noch nutzbar sind %free%. + + Maximum duration of {{ value }} hours exceeded. + Erlaubt sind max. {{ value }} Stunden. + diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index 5524f9d6..5cf91f4f 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -50,6 +50,10 @@ The budget is completely used. The budget is used up. Of the available %budget%, %used% has been booked so far, %free% can still be used. + + Maximum duration of {{ value }} hours exceeded. + Maximum {{ value }} hours allowed. +