Release 1.19.7 (#3286)

* fix isWeekend() test for sunday fdow
* re-use the pattern for optgroup title
* prevent method on null
* composer update
* pre-select an option if it is the only available one
* added command to stop all active timesheets
This commit is contained in:
Kevin Papst
2022-05-07 23:47:32 +02:00
committed by GitHub
parent 100f8a5165
commit c8098b2e00
31 changed files with 818 additions and 484 deletions

View File

@@ -0,0 +1,48 @@
<?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\Command;
use App\Timesheet\TimesheetService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @codeCoverageIgnore
*/
class TimesheetStopAllCommand extends Command
{
private $timesheetService;
public function __construct(TimesheetService $timesheetService)
{
parent::__construct();
$this->timesheetService = $timesheetService;
}
protected function configure(): void
{
$this->setName('kimai:timesheet:stop-all');
$this->setDescription('Stop all running timesheets immediately');
}
protected function execute(InputInterface $input, OutputInterface $output): ?int
{
$amount = $this->timesheetService->stopAll();
if (!$output->isQuiet()) {
$io = new SymfonyStyle($input, $output);
$io->success(sprintf('Stopped %s timesheet records.', $amount));
}
return 0;
}
}

View File

@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '1.19.6';
public const VERSION = '1.19.7';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 11906;
public const VERSION_ID = 11907;
/**
* The current release status, either "stable" or "dev"
*/

View File

@@ -86,6 +86,10 @@ abstract class AbstractUserReportController extends AbstractController
/** @var StatisticDate $date */
foreach ($activityValues['data']->getData() as $date) {
$statisticDate = $dailyProjectStatistic->getByDateTime($date->getDate());
if ($statisticDate === null) {
// this should not happen, but sometimes it does ...
continue;
}
$statisticDate->setTotalDuration($statisticDate->getTotalDuration() + $date->getTotalDuration());
$statisticDate->setTotalRate($statisticDate->getTotalRate() + $date->getTotalRate());
$statisticDate->setTotalInternalRate($statisticDate->getTotalInternalRate() + $date->getTotalInternalRate());

View File

@@ -0,0 +1,60 @@
<?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\Helper;
use App\Configuration\SystemConfiguration;
use App\Entity\Activity;
final class ActivityHelper
{
public const PATTERN_NAME = '{name}';
public const PATTERN_COMMENT = '{comment}';
public const PATTERN_SPACER = '{spacer}';
public const SPACER = ' - ';
private $configuration;
private $pattern;
public function __construct(SystemConfiguration $configuration)
{
$this->configuration = $configuration;
}
public function getChoicePattern(): string
{
if ($this->pattern === null) {
$this->pattern = $this->configuration->find('activity.choice_pattern');
if ($this->pattern === null || stripos($this->pattern, '{') === false || stripos($this->pattern, '}') === false) {
$this->pattern = self::PATTERN_NAME;
}
$this->pattern = str_replace(self::PATTERN_SPACER, self::SPACER, $this->pattern);
}
return $this->pattern;
}
public function getChoiceLabel(Activity $activity): string
{
$name = $this->getChoicePattern();
$name = str_replace(self::PATTERN_NAME, $activity->getName(), $name);
$name = str_replace(self::PATTERN_COMMENT, $activity->getComment() ?? '', $name);
$name = ltrim($name, self::SPACER);
$name = rtrim($name, self::SPACER);
if ($name === '' || $name === self::SPACER) {
$name = $activity->getName();
}
return substr($name, 0, 110);
}
}

View File

@@ -0,0 +1,64 @@
<?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\Helper;
use App\Configuration\SystemConfiguration;
use App\Entity\Customer;
final class CustomerHelper
{
public const PATTERN_NAME = '{name}';
public const PATTERN_NUMBER = '{number}';
public const PATTERN_COMPANY = '{company}';
public const PATTERN_COMMENT = '{comment}';
public const PATTERN_SPACER = '{spacer}';
public const SPACER = ' - ';
private $configuration;
private $pattern;
public function __construct(SystemConfiguration $configuration)
{
$this->configuration = $configuration;
}
public function getChoicePattern(): string
{
if ($this->pattern === null) {
$this->pattern = $this->configuration->find('customer.choice_pattern');
if ($this->pattern === null || stripos($this->pattern, '{') === false || stripos($this->pattern, '}') === false) {
$this->pattern = self::PATTERN_NAME;
}
$this->pattern = str_replace(self::PATTERN_SPACER, self::SPACER, $this->pattern);
}
return $this->pattern;
}
public function getChoiceLabel(Customer $customer): string
{
$name = $this->getChoicePattern();
$name = str_replace(self::PATTERN_NAME, $customer->getName(), $name);
$name = str_replace(self::PATTERN_COMMENT, $customer->getComment() ?? '', $name);
$name = str_replace(self::PATTERN_NUMBER, $customer->getNumber() ?? '', $name);
$name = str_replace(self::PATTERN_COMPANY, $customer->getCompany() ?? '', $name);
$name = ltrim($name, self::SPACER);
$name = rtrim($name, self::SPACER);
if ($name === '' || $name === self::SPACER) {
$name = $customer->getName();
}
return substr($name, 0, 110);
}
}

View File

@@ -0,0 +1,87 @@
<?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\Helper;
use App\Configuration\SystemConfiguration;
use App\Entity\Project;
use App\Utils\LocaleSettings;
final class ProjectHelper
{
public const PATTERN_NAME = '{name}';
public const PATTERN_COMMENT = '{comment}';
public const PATTERN_ORDERNUMBER = '{ordernumber}';
public const PATTERN_DATERANGE = '{daterange}';
public const PATTERN_START = '{start}';
public const PATTERN_END = '{end}';
public const PATTERN_SPACER = '{spacer}';
public const SPACER = ' - ';
private $configuration;
private $localeSettings;
private $dateFormat;
private $pattern;
public function __construct(SystemConfiguration $configuration, LocaleSettings $localeSettings)
{
$this->configuration = $configuration;
$this->localeSettings = $localeSettings;
}
public function getChoicePattern(): string
{
if ($this->pattern === null) {
$this->pattern = $this->configuration->find('project.choice_pattern');
if ($this->pattern === null || stripos($this->pattern, '{') === false || stripos($this->pattern, '}') === false) {
$this->pattern = self::PATTERN_NAME;
}
$this->pattern = str_replace(self::PATTERN_DATERANGE, self::PATTERN_START . '-' . self::PATTERN_END, $this->pattern);
$this->pattern = str_replace(self::PATTERN_SPACER, self::SPACER, $this->pattern);
}
return $this->pattern;
}
public function getChoiceLabel(Project $project): string
{
if ($this->dateFormat === null) {
$this->dateFormat = $this->localeSettings->getDateFormat();
}
$start = '?';
if ($project->getStart() !== null) {
$start = $project->getStart()->format($this->dateFormat);
}
$end = '?';
if ($project->getEnd() !== null) {
$end = $project->getEnd()->format($this->dateFormat);
}
$name = $this->getChoicePattern();
$name = str_replace(self::PATTERN_NAME, $project->getName(), $name);
$name = str_replace(self::PATTERN_COMMENT, $project->getComment() ?? '', $name);
$name = str_replace(self::PATTERN_ORDERNUMBER, $project->getOrderNumber() ?? '', $name);
$name = str_replace(self::PATTERN_START, $start, $name);
$name = str_replace(self::PATTERN_END, $end, $name);
$name = ltrim($name, self::SPACER);
$name = rtrim($name, self::SPACER);
$name = str_replace('- ?-?', '', $name);
if ($name === '' || $name === self::SPACER) {
$name = $project->getName();
}
return substr($name, 0, 110);
}
}

View File

@@ -9,8 +9,9 @@
namespace App\Form\Type;
use App\Configuration\SystemConfiguration;
use App\Entity\Activity;
use App\Form\Helper\ActivityHelper;
use App\Form\Helper\ProjectHelper;
use App\Repository\ActivityRepository;
use App\Repository\Query\ActivityFormTypeQuery;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
@@ -25,48 +26,18 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class ActivityType extends AbstractType
{
public const PATTERN_NAME = '{name}';
public const PATTERN_COMMENT = '{comment}';
public const PATTERN_SPACER = '{spacer}';
public const SPACER = ' - ';
private $activityHelper;
private $projectHelper;
private $configuration;
private $pattern;
public function __construct(SystemConfiguration $configuration)
public function __construct(ActivityHelper $activityHelper, ProjectHelper $projectHelper)
{
$this->configuration = $configuration;
}
private function getPattern(): string
{
if ($this->pattern === null) {
$this->pattern = $this->configuration->find('activity.choice_pattern');
if ($this->pattern === null || stripos($this->pattern, '{') === false || stripos($this->pattern, '}') === false) {
$this->pattern = self::PATTERN_NAME;
}
$this->pattern = str_replace(self::PATTERN_SPACER, self::SPACER, $this->pattern);
}
return $this->pattern;
$this->activityHelper = $activityHelper;
$this->projectHelper = $projectHelper;
}
public function getChoiceLabel(Activity $activity): string
{
$name = $this->getPattern();
$name = str_replace(self::PATTERN_NAME, $activity->getName(), $name);
$name = str_replace(self::PATTERN_COMMENT, $activity->getComment() ?? '', $name);
$name = ltrim($name, self::SPACER);
$name = rtrim($name, self::SPACER);
if ($name === '' || $name === self::SPACER) {
$name = $activity->getName();
}
return substr($name, 0, 110);
return $this->activityHelper->getChoiceLabel($activity);
}
/**
@@ -78,7 +49,7 @@ class ActivityType extends AbstractType
return null;
}
return $activity->getProject()->getName();
return $this->projectHelper->getChoiceLabel($activity->getProject());
}
/**
@@ -141,7 +112,7 @@ class ActivityType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['attr'] = array_merge($view->vars['attr'], [
'data-option-pattern' => $this->getPattern(),
'data-option-pattern' => $this->activityHelper->getChoicePattern(),
]);
}

View File

@@ -9,6 +9,7 @@
namespace App\Form\Type;
use App\Form\Helper\ActivityHelper;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -33,14 +34,14 @@ class ActivityTypePatternType extends AbstractType
{
$name = $this->translator->trans('label.name');
$comment = $this->translator->trans('label.description');
$spacer = ActivityType::SPACER;
$spacer = ActivityHelper::SPACER;
$resolver->setDefaults([
'label' => 'label.choice_pattern',
'choices' => [
$name => ActivityType::PATTERN_NAME,
$comment => ActivityType::PATTERN_COMMENT,
$name . $spacer . $comment => ActivityType::PATTERN_NAME . ActivityType::PATTERN_SPACER . ActivityType::PATTERN_COMMENT,
$name => ActivityHelper::PATTERN_NAME,
$comment => ActivityHelper::PATTERN_COMMENT,
$name . $spacer . $comment => ActivityHelper::PATTERN_NAME . ActivityHelper::PATTERN_SPACER . ActivityHelper::PATTERN_COMMENT,
]
]);
}

View File

@@ -9,8 +9,8 @@
namespace App\Form\Type;
use App\Configuration\SystemConfiguration;
use App\Entity\Customer;
use App\Form\Helper\CustomerHelper;
use App\Repository\CustomerRepository;
use App\Repository\Query\CustomerFormTypeQuery;
use App\Repository\Query\ProjectQuery;
@@ -26,52 +26,16 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class CustomerType extends AbstractType
{
public const PATTERN_NAME = '{name}';
public const PATTERN_NUMBER = '{number}';
public const PATTERN_COMPANY = '{company}';
public const PATTERN_COMMENT = '{comment}';
public const PATTERN_SPACER = '{spacer}';
public const SPACER = ' - ';
private $customerHelper;
private $configuration;
private $pattern;
public function __construct(SystemConfiguration $configuration)
public function __construct(CustomerHelper $customerHelper)
{
$this->configuration = $configuration;
}
private function getPattern(): string
{
if ($this->pattern === null) {
$this->pattern = $this->configuration->find('customer.choice_pattern');
if ($this->pattern === null || stripos($this->pattern, '{') === false || stripos($this->pattern, '}') === false) {
$this->pattern = self::PATTERN_NAME;
}
$this->pattern = str_replace(self::PATTERN_SPACER, self::SPACER, $this->pattern);
}
return $this->pattern;
$this->customerHelper = $customerHelper;
}
public function getChoiceLabel(Customer $customer): string
{
$name = $this->getPattern();
$name = str_replace(self::PATTERN_NAME, $customer->getName(), $name);
$name = str_replace(self::PATTERN_COMMENT, $customer->getComment() ?? '', $name);
$name = str_replace(self::PATTERN_NUMBER, $customer->getNumber() ?? '', $name);
$name = str_replace(self::PATTERN_COMPANY, $customer->getCompany() ?? '', $name);
$name = ltrim($name, self::SPACER);
$name = rtrim($name, self::SPACER);
if ($name === '' || $name === self::SPACER) {
$name = $customer->getName();
}
return substr($name, 0, 110);
return $this->customerHelper->getChoiceLabel($customer);
}
public function getChoiceAttributes(Customer $customer, $key, $value): array
@@ -158,7 +122,7 @@ class CustomerType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['attr'] = array_merge($view->vars['attr'], [
'data-option-pattern' => $this->getPattern(),
'data-option-pattern' => $this->customerHelper->getChoicePattern(),
]);
}

View File

@@ -9,6 +9,7 @@
namespace App\Form\Type;
use App\Form\Helper\CustomerHelper;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -35,22 +36,22 @@ class CustomerTypePatternType extends AbstractType
$company = $this->translator->trans('label.company');
$number = $this->translator->trans('label.number');
$comment = $this->translator->trans('label.description');
$spacer = CustomerType::SPACER;
$spacer = CustomerHelper::SPACER;
$resolver->setDefaults([
'label' => 'label.choice_pattern',
'choices' => [
$name => CustomerType::PATTERN_NAME,
$company => CustomerType::PATTERN_COMPANY,
$number => CustomerType::PATTERN_NUMBER,
$comment => CustomerType::PATTERN_COMMENT,
$name . $spacer . $company => CustomerType::PATTERN_NAME . CustomerType::PATTERN_SPACER . CustomerType::PATTERN_COMPANY,
$name . $spacer . $number => CustomerType::PATTERN_NAME . CustomerType::PATTERN_SPACER . CustomerType::PATTERN_NUMBER,
$name . $spacer . $comment => CustomerType::PATTERN_NAME . CustomerType::PATTERN_SPACER . CustomerType::PATTERN_COMMENT,
$number . $spacer . $name => CustomerType::PATTERN_NUMBER . CustomerType::PATTERN_SPACER . CustomerType::PATTERN_NAME,
$number . $spacer . $company => CustomerType::PATTERN_NUMBER . CustomerType::PATTERN_SPACER . CustomerType::PATTERN_COMPANY,
$number . $spacer . $comment => CustomerType::PATTERN_NUMBER . CustomerType::PATTERN_SPACER . CustomerType::PATTERN_COMMENT,
$company . $spacer . $comment => CustomerType::PATTERN_COMPANY . CustomerType::PATTERN_SPACER . CustomerType::PATTERN_COMMENT,
$name => CustomerHelper::PATTERN_NAME,
$company => CustomerHelper::PATTERN_COMPANY,
$number => CustomerHelper::PATTERN_NUMBER,
$comment => CustomerHelper::PATTERN_COMMENT,
$name . $spacer . $company => CustomerHelper::PATTERN_NAME . CustomerHelper::PATTERN_SPACER . CustomerHelper::PATTERN_COMPANY,
$name . $spacer . $number => CustomerHelper::PATTERN_NAME . CustomerHelper::PATTERN_SPACER . CustomerHelper::PATTERN_NUMBER,
$name . $spacer . $comment => CustomerHelper::PATTERN_NAME . CustomerHelper::PATTERN_SPACER . CustomerHelper::PATTERN_COMMENT,
$number . $spacer . $name => CustomerHelper::PATTERN_NUMBER . CustomerHelper::PATTERN_SPACER . CustomerHelper::PATTERN_NAME,
$number . $spacer . $company => CustomerHelper::PATTERN_NUMBER . CustomerHelper::PATTERN_SPACER . CustomerHelper::PATTERN_COMPANY,
$number . $spacer . $comment => CustomerHelper::PATTERN_NUMBER . CustomerHelper::PATTERN_SPACER . CustomerHelper::PATTERN_COMMENT,
$company . $spacer . $comment => CustomerHelper::PATTERN_COMPANY . CustomerHelper::PATTERN_SPACER . CustomerHelper::PATTERN_COMMENT,
]
]);
}

View File

@@ -9,12 +9,12 @@
namespace App\Form\Type;
use App\Configuration\SystemConfiguration;
use App\Entity\Project;
use App\Form\Helper\CustomerHelper;
use App\Form\Helper\ProjectHelper;
use App\Repository\ProjectRepository;
use App\Repository\Query\ActivityQuery;
use App\Repository\Query\ProjectFormTypeQuery;
use App\Utils\LocaleSettings;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
@@ -27,74 +27,18 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class ProjectType extends AbstractType
{
public const PATTERN_NAME = '{name}';
public const PATTERN_COMMENT = '{comment}';
public const PATTERN_ORDERNUMBER = '{ordernumber}';
public const PATTERN_DATERANGE = '{daterange}';
public const PATTERN_START = '{start}';
public const PATTERN_END = '{end}';
public const PATTERN_SPACER = '{spacer}';
public const SPACER = ' - ';
private $projectHelper;
private $customerHelper;
private $configuration;
private $localeSettings;
private $dateFormat;
private $pattern;
public function __construct(SystemConfiguration $configuration, LocaleSettings $localeSettings)
public function __construct(ProjectHelper $projectHelper, CustomerHelper $customerHelper)
{
$this->configuration = $configuration;
$this->localeSettings = $localeSettings;
}
private function getPattern(): string
{
if ($this->pattern === null) {
$this->pattern = $this->configuration->find('project.choice_pattern');
if ($this->pattern === null || stripos($this->pattern, '{') === false || stripos($this->pattern, '}') === false) {
$this->pattern = self::PATTERN_NAME;
}
$this->pattern = str_replace(self::PATTERN_DATERANGE, self::PATTERN_START . '-' . self::PATTERN_END, $this->pattern);
$this->pattern = str_replace(self::PATTERN_SPACER, self::SPACER, $this->pattern);
}
return $this->pattern;
$this->projectHelper = $projectHelper;
$this->customerHelper = $customerHelper;
}
public function getChoiceLabel(Project $project): string
{
if ($this->dateFormat === null) {
$this->dateFormat = $this->localeSettings->getDateFormat();
}
$start = '?';
if ($project->getStart() !== null) {
$start = $project->getStart()->format($this->dateFormat);
}
$end = '?';
if ($project->getEnd() !== null) {
$end = $project->getEnd()->format($this->dateFormat);
}
$name = $this->getPattern();
$name = str_replace(self::PATTERN_NAME, $project->getName(), $name);
$name = str_replace(self::PATTERN_COMMENT, $project->getComment() ?? '', $name);
$name = str_replace(self::PATTERN_ORDERNUMBER, $project->getOrderNumber() ?? '', $name);
$name = str_replace(self::PATTERN_START, $start, $name);
$name = str_replace(self::PATTERN_END, $end, $name);
$name = ltrim($name, self::SPACER);
$name = rtrim($name, self::SPACER);
$name = str_replace('- ?-?', '', $name);
if ($name === '' || $name === self::SPACER) {
$name = $project->getName();
}
return substr($name, 0, 110);
return $this->projectHelper->getChoiceLabel($project);
}
/**
@@ -128,7 +72,7 @@ class ProjectType extends AbstractType
'choice_label' => [$this, 'getChoiceLabel'],
'choice_attr' => [$this, 'getChoiceAttributes'],
'group_by' => function (Project $project, $key, $index) {
return $project->getCustomer()->getName();
return $this->customerHelper->getChoiceLabel($project->getCustomer());
},
'query_builder_for_user' => true,
'activity_enabled' => false,
@@ -197,7 +141,7 @@ class ProjectType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['attr'] = array_merge($view->vars['attr'], [
'data-option-pattern' => $this->getPattern(),
'data-option-pattern' => $this->projectHelper->getChoicePattern(),
]);
}

View File

@@ -9,6 +9,7 @@
namespace App\Form\Type;
use App\Form\Helper\ProjectHelper;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -37,16 +38,16 @@ class ProjectTypePatternType extends AbstractType
$projectStart = $this->translator->trans('label.project_start');
$projectEnd = $this->translator->trans('label.project_end');
$spacer = ProjectType::SPACER;
$spacer = ProjectHelper::SPACER;
$resolver->setDefaults([
'label' => 'label.choice_pattern',
'choices' => [
$name => ProjectType::PATTERN_NAME,
$comment => ProjectType::PATTERN_COMMENT,
$name . $spacer . $orderNumber => ProjectType::PATTERN_NAME . ProjectType::PATTERN_SPACER . ProjectType::PATTERN_ORDERNUMBER,
$name . $spacer . $comment => ProjectType::PATTERN_NAME . ProjectType::PATTERN_SPACER . ProjectType::PATTERN_COMMENT,
$name . $spacer . $projectStart . '-' . $projectEnd => ProjectType::PATTERN_NAME . ProjectType::PATTERN_SPACER . ProjectType::PATTERN_DATERANGE,
$name => ProjectHelper::PATTERN_NAME,
$comment => ProjectHelper::PATTERN_COMMENT,
$name . $spacer . $orderNumber => ProjectHelper::PATTERN_NAME . ProjectHelper::PATTERN_SPACER . ProjectHelper::PATTERN_ORDERNUMBER,
$name . $spacer . $comment => ProjectHelper::PATTERN_NAME . ProjectHelper::PATTERN_SPACER . ProjectHelper::PATTERN_COMMENT,
$name . $spacer . $projectStart . '-' . $projectEnd => ProjectHelper::PATTERN_NAME . ProjectHelper::PATTERN_SPACER . ProjectHelper::PATTERN_DATERANGE,
]
]);
}

View File

@@ -223,10 +223,11 @@ final class TimesheetService
* But also to check that all required data is set.
*
* @param Timesheet $timesheet
* @param bool $validate
* @throws ValidationException for already stopped timesheets
* @throws ValidationFailedException
*/
public function stopTimesheet(Timesheet $timesheet): void
public function stopTimesheet(Timesheet $timesheet, bool $validate = true): void
{
if (null !== $timesheet->getEnd()) {
// timesheet already stopped, nothing to do. in previous version, this method did throw a:
@@ -242,7 +243,9 @@ final class TimesheetService
$timesheet->setBegin($begin);
$timesheet->setEnd($now);
$this->validateTimesheet($timesheet);
if ($validate) {
$this->validateTimesheet($timesheet);
}
$this->dispatcher->dispatch(new TimesheetStopPreEvent($timesheet));
$this->repository->save($timesheet);
@@ -328,4 +331,17 @@ final class TimesheetService
{
return $this->trackingModeService->getActiveMode();
}
public function stopAll(): int
{
$activeEntries = $this->repository->getActiveEntries();
$counter = 0;
foreach ($activeEntries as $timesheet) {
$this->stopTimesheet($timesheet, false);
$counter++;
}
return $counter;
}
}

View File

@@ -28,6 +28,10 @@ final class LocaleFormatExtensions extends AbstractExtension
private $formats;
private $security;
/**
* @var bool|null
*/
private $fdowSunday = null;
/**
* @var LocaleFormats|null
*/
@@ -77,14 +81,7 @@ final class LocaleFormatExtensions extends AbstractExtension
public function getTests()
{
return [
new TwigTest('weekend', function ($dateTime) {
if (!$dateTime instanceof \DateTime) {
return false;
}
$day = (int) $dateTime->format('w');
return ($day === 0 || $day === 6);
}),
new TwigTest('weekend', [$this, 'isWeekend']),
new TwigTest('today', function ($dateTime) {
if (!$dateTime instanceof \DateTime) {
return false;
@@ -149,6 +146,35 @@ final class LocaleFormatExtensions extends AbstractExtension
return $this->locale;
}
/**
* @param DateTime $dateTime
* @return bool
*/
public function isWeekend($dateTime): bool
{
if (!$dateTime instanceof \DateTime) {
return false;
}
$day = (int) $dateTime->format('w');
if ($this->fdowSunday === null) {
/** @var User|null $user */
$user = $this->security->getUser();
if ($user !== null) {
$this->fdowSunday = $user->isFirstDayOfWeekSunday();
} else {
$this->fdowSunday = false;
}
}
if ($this->fdowSunday) {
return ($day === 5 || $day === 6);
}
return ($day === 0 || $day === 6);
}
/**
* @param DateTime|string $date
* @return string