diff --git a/src/Configuration/SystemConfiguration.php b/src/Configuration/SystemConfiguration.php index 07e58e94..49ad1e02 100644 --- a/src/Configuration/SystemConfiguration.php +++ b/src/Configuration/SystemConfiguration.php @@ -208,6 +208,11 @@ class SystemConfiguration implements SystemBundleConfiguration return (string) $this->find('timesheet.rules.lockdown_grace_period'); } + public function getTimesheetLockdownTimeZone(): ?string + { + return $this->find('timesheet.rules.lockdown_period_timezone'); + } + public function isTimesheetLockdownActive(): bool { return !empty($this->find('timesheet.rules.lockdown_period_start')) && !empty($this->find('timesheet.rules.lockdown_period_end')); diff --git a/src/Controller/SystemConfigurationController.php b/src/Controller/SystemConfigurationController.php index 226ed81e..731fc387 100644 --- a/src/Controller/SystemConfigurationController.php +++ b/src/Controller/SystemConfigurationController.php @@ -22,6 +22,7 @@ use App\Form\Type\LanguageType; use App\Form\Type\MinuteIncrementType; use App\Form\Type\RoundingModeType; use App\Form\Type\SkinType; +use App\Form\Type\TimezoneType; use App\Form\Type\TrackingModeType; use App\Form\Type\WeekDaysType; use App\Form\Type\YesNoType; @@ -37,7 +38,6 @@ use Symfony\Component\Form\Extension\Core\Type\CountryType; use Symfony\Component\Form\Extension\Core\Type\CurrencyType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\TextType; -use Symfony\Component\Form\Extension\Core\Type\TimezoneType; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -232,18 +232,32 @@ final class SystemConfigurationController extends AbstractController $dateFormat = 'D, d M Y H:i:s'; if ($this->configurations->isTimesheetLockdownActive()) { + $userTimezone = $this->getDateTimeFactory()->getTimezone(); + $timezone = $this->configurations->getTimesheetLockdownTimeZone(); + + if ($timezone !== null) { + $timezone = new \DateTimeZone($timezone); + } + + if ($timezone === null) { + $timezone = $userTimezone; + } + try { if (!empty($this->configurations->getTimesheetLockdownPeriodStart())) { - $lockdownStartHelp = $this->getDateTimeFactory()->createDateTime($this->configurations->getTimesheetLockdownPeriodStart()); + $lockdownStartHelp = new \DateTime($this->configurations->getTimesheetLockdownPeriodStart(), $timezone); + $lockdownStartHelp->setTimezone($userTimezone); $lockdownStartHelp = $lockdownStartHelp->format($dateFormat); } if (!empty($this->configurations->getTimesheetLockdownPeriodEnd())) { - $lockdownEndHelp = $this->getDateTimeFactory()->createDateTime($this->configurations->getTimesheetLockdownPeriodEnd()); + $lockdownEndHelp = new \DateTime($this->configurations->getTimesheetLockdownPeriodEnd(), $timezone); if (!empty($this->configurations->getTimesheetLockdownGracePeriod())) { $lockdownGraceHelp = clone $lockdownEndHelp; $lockdownGraceHelp->modify($this->configurations->getTimesheetLockdownGracePeriod()); + $lockdownGraceHelp->setTimezone($userTimezone); $lockdownGraceHelp = $lockdownGraceHelp->format($dateFormat); } + $lockdownEndHelp->setTimezone($userTimezone); $lockdownEndHelp = $lockdownEndHelp->format($dateFormat); } } catch (\Exception $ex) { @@ -276,27 +290,6 @@ final class SystemConfigurationController extends AbstractController ->setName('timesheet.rules.allow_overbooking_budget') ->setType(CheckboxType::class) ->setTranslationDomain('system-configuration'), - (new Configuration()) - ->setName('timesheet.rules.lockdown_period_start') - ->setOptions(['help' => $lockdownStartHelp]) - ->setType(TextType::class) - ->setRequired(false) - ->setConstraints([new DateTimeFormat()]) - ->setTranslationDomain('system-configuration'), - (new Configuration()) - ->setName('timesheet.rules.lockdown_period_end') - ->setOptions(['help' => $lockdownEndHelp]) - ->setType(TextType::class) - ->setRequired(false) - ->setConstraints([new DateTimeFormat()]) - ->setTranslationDomain('system-configuration'), - (new Configuration()) - ->setName('timesheet.rules.lockdown_grace_period') - ->setOptions(['help' => $lockdownGraceHelp]) - ->setType(TextType::class) - ->setRequired(false) - ->setConstraints([new DateTimeFormat()]) - ->setTranslationDomain('system-configuration'), (new Configuration()) ->setName('timesheet.active_entries.hard_limit') ->setType(IntegerType::class) @@ -327,6 +320,36 @@ final class SystemConfigurationController extends AbstractController new GreaterThanOrEqual(['value' => 0]) ]), ]), + (new SystemConfigurationModel()) + ->setSection(SystemConfigurationModel::SECTION_LOCKDOWN) + ->setConfiguration([ + (new Configuration()) + ->setName('timesheet.rules.lockdown_period_start') + ->setOptions(['help' => $lockdownStartHelp]) + ->setType(TextType::class) + ->setRequired(false) + ->setConstraints([new DateTimeFormat()]) + ->setTranslationDomain('system-configuration'), + (new Configuration()) + ->setName('timesheet.rules.lockdown_period_end') + ->setOptions(['help' => $lockdownEndHelp]) + ->setType(TextType::class) + ->setRequired(false) + ->setConstraints([new DateTimeFormat()]) + ->setTranslationDomain('system-configuration'), + (new Configuration()) + ->setName('timesheet.rules.lockdown_period_timezone') + ->setType(TimezoneType::class) + ->setRequired(false) + ->setTranslationDomain('system-configuration'), + (new Configuration()) + ->setName('timesheet.rules.lockdown_grace_period') + ->setOptions(['help' => $lockdownGraceHelp]) + ->setType(TextType::class) + ->setRequired(false) + ->setConstraints([new DateTimeFormat()]) + ->setTranslationDomain('system-configuration'), + ]), (new SystemConfigurationModel()) ->setSection(SystemConfigurationModel::SECTION_ROUNDING) ->setConfiguration([ diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 67ece1b0..31a7af76 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -235,6 +235,9 @@ class Configuration implements ConfigurationInterface ->scalarNode('lockdown_period_end') ->defaultNull() ->end() + ->scalarNode('lockdown_period_timezone') + ->defaultNull() + ->end() ->scalarNode('lockdown_grace_period') ->defaultNull() ->end() diff --git a/src/EventSubscriber/UserPreferenceSubscriber.php b/src/EventSubscriber/UserPreferenceSubscriber.php index 1c9fca18..bafcad7c 100644 --- a/src/EventSubscriber/UserPreferenceSubscriber.php +++ b/src/EventSubscriber/UserPreferenceSubscriber.php @@ -21,12 +21,12 @@ use App\Form\Type\LanguageType; use App\Form\Type\ReportType; use App\Form\Type\SkinType; use App\Form\Type\ThemeLayoutType; +use App\Form\Type\TimezoneType; use App\Reporting\ReportingService; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; -use Symfony\Component\Form\Extension\Core\Type\TimezoneType; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Validator\Constraints\Range; diff --git a/src/Form/CustomerEditForm.php b/src/Form/CustomerEditForm.php index 21059272..3cb57bdb 100644 --- a/src/Form/CustomerEditForm.php +++ b/src/Form/CustomerEditForm.php @@ -11,13 +11,13 @@ namespace App\Form; use App\Entity\Customer; use App\Form\Type\MailType; +use App\Form\Type\TimezoneType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CountryType; use Symfony\Component\Form\Extension\Core\Type\CurrencyType; use Symfony\Component\Form\Extension\Core\Type\TelType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; -use Symfony\Component\Form\Extension\Core\Type\TimezoneType; use Symfony\Component\Form\Extension\Core\Type\UrlType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; diff --git a/src/Form/Model/SystemConfiguration.php b/src/Form/Model/SystemConfiguration.php index 00fd19a4..de14ada9 100644 --- a/src/Form/Model/SystemConfiguration.php +++ b/src/Form/Model/SystemConfiguration.php @@ -12,6 +12,7 @@ namespace App\Form\Model; class SystemConfiguration { public const SECTION_ROUNDING = 'rounding'; + public const SECTION_LOCKDOWN = 'lockdown_period'; public const SECTION_TIMESHEET = 'timesheet'; public const SECTION_FORM_INVOICE = 'invoice'; public const SECTION_FORM_CUSTOMER = 'form_customer'; diff --git a/src/Form/Type/TimezoneType.php b/src/Form/Type/TimezoneType.php new file mode 100644 index 00000000..e42c0b98 --- /dev/null +++ b/src/Form/Type/TimezoneType.php @@ -0,0 +1,37 @@ +setDefaults([ + 'intl' => false, + ]); + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return BaseTimezoneType::class; + } +} diff --git a/src/Form/UserEditType.php b/src/Form/UserEditType.php index 24959e3f..da8f4c26 100644 --- a/src/Form/UserEditType.php +++ b/src/Form/UserEditType.php @@ -12,11 +12,11 @@ namespace App\Form; use App\Entity\User; use App\Form\Type\AvatarType; use App\Form\Type\LanguageType; +use App\Form\Type\TimezoneType; use App\Form\Type\YesNoType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\TextType; -use Symfony\Component\Form\Extension\Core\Type\TimezoneType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; diff --git a/src/Timesheet/LockdownService.php b/src/Timesheet/LockdownService.php index 9c5a1c1d..f55d9b08 100644 --- a/src/Timesheet/LockdownService.php +++ b/src/Timesheet/LockdownService.php @@ -55,10 +55,17 @@ final class LockdownService $lockedStart = $this->configuration->getTimesheetLockdownPeriodStart(); $lockedEnd = $this->configuration->getTimesheetLockdownPeriodEnd(); $gracePeriod = $this->configuration->getTimesheetLockdownGracePeriod(); + $timezone = $this->configuration->getTimesheetLockdownTimeZone(); + + if ($timezone === null) { + $timezone = $timesheetStart->getTimezone(); + } else { + $timezone = new \DateTimeZone($timezone); + } try { - $lockdownStart = new \DateTime($lockedStart, $timesheetStart->getTimezone()); - $lockdownEnd = new \DateTime($lockedEnd, $timesheetStart->getTimezone()); + $lockdownStart = new \DateTime($lockedStart, $timezone); + $lockdownEnd = new \DateTime($lockedEnd, $timezone); $lockdownGrace = clone $lockdownEnd; if (!empty($gracePeriod)) { $lockdownGrace->modify($gracePeriod); diff --git a/tests/Controller/SystemConfigurationControllerTest.php b/tests/Controller/SystemConfigurationControllerTest.php index be1915ee..82b6beb3 100644 --- a/tests/Controller/SystemConfigurationControllerTest.php +++ b/tests/Controller/SystemConfigurationControllerTest.php @@ -72,6 +72,7 @@ class SystemConfigurationControllerTest extends ControllerBaseTest { return [ ['form[name=system_configuration_form_timesheet]', $this->createUrl('/admin/system-config/update/timesheet')], + ['form[name=system_configuration_form_lockdown_period]', $this->createUrl('/admin/system-config/update/lockdown_period')], ['form[name=system_configuration_form_invoice]', $this->createUrl('/admin/system-config/update/invoice')], ['form[name=system_configuration_form_rounding]', $this->createUrl('/admin/system-config/update/rounding')], ['form[name=system_configuration_form_form_customer]', $this->createUrl('/admin/system-config/update/form_customer')], @@ -102,9 +103,6 @@ class SystemConfigurationControllerTest extends ControllerBaseTest ['name' => 'timesheet.rules.allow_future_times', 'value' => false], ['name' => 'timesheet.rules.allow_overlapping_records', 'value' => false], ['name' => 'timesheet.rules.allow_overbooking_budget', 'value' => false], - ['name' => 'timesheet.rules.lockdown_period_start', 'value' => null], - ['name' => 'timesheet.rules.lockdown_period_end', 'value' => null], - ['name' => 'timesheet.rules.lockdown_grace_period', 'value' => null], ['name' => 'timesheet.active_entries.hard_limit', 'value' => 99], ['name' => 'timesheet.active_entries.soft_limit', 'value' => 77], ] @@ -124,6 +122,41 @@ class SystemConfigurationControllerTest extends ControllerBaseTest $this->assertEquals(77, $configService->find('timesheet.active_entries.soft_limit')); } + public function testUpdateLockdownPeriodConfig() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN); + $this->assertAccessIsGranted($client, '/admin/system-config/'); + + $configService = static::$kernel->getContainer()->get(SystemConfiguration::class); + $this->assertNull($configService->find('timesheet.rules.lockdown_period_start')); + $this->assertNull($configService->find('timesheet.rules.lockdown_period_end')); + $this->assertNull($configService->find('timesheet.rules.lockdown_period_timezone')); + $this->assertNull($configService->find('timesheet.rules.lockdown_grace_period')); + + $form = $client->getCrawler()->filter('form[name=system_configuration_form_lockdown_period]')->form(); + $client->submit($form, [ + 'system_configuration_form_lockdown_period' => [ + 'configuration' => [ + ['name' => 'timesheet.rules.lockdown_period_start', 'value' => 'first day of last month 01:23:45'], + ['name' => 'timesheet.rules.lockdown_period_end', 'value' => 'last day of last month 23:01:45'], + ['name' => 'timesheet.rules.lockdown_period_timezone', 'value' => 'Africa/Bangui'], + ['name' => 'timesheet.rules.lockdown_grace_period', 'value' => '+ 12 hours'], + ] + ] + ]); + + $this->assertIsRedirect($client, $this->createUrl('/admin/system-config/')); + $client->followRedirect(); + $this->assertTrue($client->getResponse()->isSuccessful()); + $this->assertHasFlashSaveSuccess($client); + + $configService = static::$kernel->getContainer()->get(SystemConfiguration::class); + $this->assertEquals('first day of last month 01:23:45', $configService->find('timesheet.rules.lockdown_period_start')); + $this->assertEquals('last day of last month 23:01:45', $configService->find('timesheet.rules.lockdown_period_end')); + $this->assertEquals('Africa/Bangui', $configService->find('timesheet.rules.lockdown_period_timezone')); + $this->assertEquals('+ 12 hours', $configService->find('timesheet.rules.lockdown_grace_period')); + } + public function testUpdateTimesheetConfigValidation() { $this->assertFormHasValidationError( @@ -138,9 +171,6 @@ class SystemConfigurationControllerTest extends ControllerBaseTest ['name' => 'timesheet.rules.allow_future_times', 'value' => 1], ['name' => 'timesheet.rules.allow_overlapping_records', 'value' => 1], ['name' => 'timesheet.rules.allow_overbooking_budget', 'value' => 1], - ['name' => 'timesheet.rules.lockdown_period_start', 'value' => 'first day of last month'], - ['name' => 'timesheet.rules.lockdown_period_end', 'value' => 'first day of last month'], - ['name' => 'timesheet.rules.lockdown_grace_period', 'value' => '+10 days'], ['name' => 'timesheet.active_entries.hard_limit', 'value' => -1], ['name' => 'timesheet.active_entries.soft_limit', 'value' => -1], ] @@ -148,8 +178,8 @@ class SystemConfigurationControllerTest extends ControllerBaseTest ], [ '#system_configuration_form_timesheet_configuration_0_value', // mode - '#system_configuration_form_timesheet_configuration_8_value', // hard_limit - '#system_configuration_form_timesheet_configuration_9_value', // soft_limit + '#system_configuration_form_timesheet_configuration_5_value', // hard_limit + '#system_configuration_form_timesheet_configuration_6_value', // soft_limit ], true ); diff --git a/tests/Controller/TimesheetControllerTest.php b/tests/Controller/TimesheetControllerTest.php index 29f10b69..79764a5e 100644 --- a/tests/Controller/TimesheetControllerTest.php +++ b/tests/Controller/TimesheetControllerTest.php @@ -375,9 +375,6 @@ class TimesheetControllerTest extends ControllerBaseTest ['name' => 'timesheet.rules.allow_future_times', 'value' => true], ['name' => 'timesheet.rules.allow_overlapping_records', 'value' => false], ['name' => 'timesheet.rules.allow_overbooking_budget', 'value' => true], - ['name' => 'timesheet.rules.lockdown_period_start', 'value' => null], - ['name' => 'timesheet.rules.lockdown_period_end', 'value' => null], - ['name' => 'timesheet.rules.lockdown_grace_period', 'value' => null], ['name' => 'timesheet.active_entries.hard_limit', 'value' => 1], ['name' => 'timesheet.active_entries.soft_limit', 'value' => 1], ] diff --git a/tests/DependencyInjection/AppExtensionTest.php b/tests/DependencyInjection/AppExtensionTest.php index c91bef61..599d652a 100644 --- a/tests/DependencyInjection/AppExtensionTest.php +++ b/tests/DependencyInjection/AppExtensionTest.php @@ -207,6 +207,7 @@ class AppExtensionTest extends TestCase 'lockdown_period_end' => null, 'lockdown_grace_period' => null, 'allow_overbooking_budget' => true, + 'lockdown_period_timezone' => null, ], 'default_begin' => 'now', 'duration_increment' => null, diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index f55f96de..6f7b351f 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -286,6 +286,7 @@ class ConfigurationTest extends TestCase 'lockdown_period_end' => null, 'lockdown_grace_period' => null, 'allow_overbooking_budget' => true, + 'lockdown_period_timezone' => null, ], 'duration_increment' => null, 'time_increment' => null, diff --git a/tests/Timesheet/LockdownServiceTest.php b/tests/Timesheet/LockdownServiceTest.php index bb37e295..fbaf6f9c 100644 --- a/tests/Timesheet/LockdownServiceTest.php +++ b/tests/Timesheet/LockdownServiceTest.php @@ -20,7 +20,7 @@ use PHPUnit\Framework\TestCase; */ class LockdownServiceTest extends TestCase { - protected function createService(?string $start, ?string $end, ?string $grace) + protected function createService(?string $start, ?string $end, ?string $grace, ?string $timezone = null) { $loader = $this->createMock(ConfigLoaderInterface::class); $config = new SystemConfiguration($loader, [ @@ -28,6 +28,7 @@ class LockdownServiceTest extends TestCase 'rules' => [ 'lockdown_period_start' => $start, 'lockdown_period_end' => $end, + 'lockdown_period_timezone' => $timezone, 'lockdown_grace_period' => $grace, ], ] diff --git a/translations/system-configuration.de.xlf b/translations/system-configuration.de.xlf index be07e9e4..dc31f486 100644 --- a/translations/system-configuration.de.xlf +++ b/translations/system-configuration.de.xlf @@ -74,6 +74,14 @@ label.timesheet.rules.allow_overlapping_records Erlaube überlappende Zeiteinträge + + lockdown_period + Gesperrter Zeitraum + + + label.timesheet.rules.lockdown_period_timezone + Zeitzone (falls leer, wird Zeitzone des jeweiligen Benutzers verwendet) + label.timesheet.rules.lockdown_period_start Start des gesperrten Zeitraums (relatives PHP Datumsformat) diff --git a/translations/system-configuration.en.xlf b/translations/system-configuration.en.xlf index 513b10f3..6ee7fe3e 100644 --- a/translations/system-configuration.en.xlf +++ b/translations/system-configuration.en.xlf @@ -74,6 +74,14 @@ label.timesheet.rules.allow_overlapping_records Allow overlapping time entries + + lockdown_period + Lockdown period + + + label.timesheet.rules.lockdown_period_timezone + Time zone (if empty, time zone of the respective user is used) + label.timesheet.rules.lockdown_period_start Lockdown period start (PHP relative date to now)