Support for changeable work contract types (#5069)
This commit is contained in:
20
src/WorkingTime/Calculator/WorkingTimeCalculator.php
Normal file
20
src/WorkingTime/Calculator/WorkingTimeCalculator.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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\WorkingTime\Calculator;
|
||||
|
||||
interface WorkingTimeCalculator
|
||||
{
|
||||
/**
|
||||
* @return int seconds
|
||||
*/
|
||||
public function getWorkHoursForDay(\DateTimeInterface $dateTime): int;
|
||||
|
||||
public function isWorkDay(\DateTimeInterface $dateTime): bool;
|
||||
}
|
||||
46
src/WorkingTime/Calculator/WorkingTimeCalculatorDay.php
Normal file
46
src/WorkingTime/Calculator/WorkingTimeCalculatorDay.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\WorkingTime\Calculator;
|
||||
|
||||
use App\Entity\User;
|
||||
|
||||
final class WorkingTimeCalculatorDay implements WorkingTimeCalculator
|
||||
{
|
||||
public const WORK_HOURS_MONDAY = 'work_monday';
|
||||
public const WORK_HOURS_TUESDAY = 'work_tuesday';
|
||||
public const WORK_HOURS_WEDNESDAY = 'work_wednesday';
|
||||
public const WORK_HOURS_THURSDAY = 'work_thursday';
|
||||
public const WORK_HOURS_FRIDAY = 'work_friday';
|
||||
public const WORK_HOURS_SATURDAY = 'work_saturday';
|
||||
public const WORK_HOURS_SUNDAY = 'work_sunday';
|
||||
|
||||
public function __construct(private readonly User $user)
|
||||
{
|
||||
}
|
||||
|
||||
public function getWorkHoursForDay(\DateTimeInterface $dateTime): int
|
||||
{
|
||||
return (int) match ($dateTime->format('N')) {
|
||||
'1' => $this->user->getPreferenceValue(self::WORK_HOURS_MONDAY, 0),
|
||||
'2' => $this->user->getPreferenceValue(self::WORK_HOURS_TUESDAY, 0),
|
||||
'3' => $this->user->getPreferenceValue(self::WORK_HOURS_WEDNESDAY, 0),
|
||||
'4' => $this->user->getPreferenceValue(self::WORK_HOURS_THURSDAY, 0),
|
||||
'5' => $this->user->getPreferenceValue(self::WORK_HOURS_FRIDAY, 0),
|
||||
'6' => $this->user->getPreferenceValue(self::WORK_HOURS_SATURDAY, 0),
|
||||
'7' => $this->user->getPreferenceValue(self::WORK_HOURS_SUNDAY, 0),
|
||||
default => throw new \Exception('Unknown day: ' . $dateTime->format('Y-m-d'))
|
||||
};
|
||||
}
|
||||
|
||||
public function isWorkDay(\DateTimeInterface $dateTime): bool
|
||||
{
|
||||
return $this->getWorkHoursForDay($dateTime) > 0;
|
||||
}
|
||||
}
|
||||
24
src/WorkingTime/Calculator/WorkingTimeCalculatorNone.php
Normal file
24
src/WorkingTime/Calculator/WorkingTimeCalculatorNone.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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\WorkingTime\Calculator;
|
||||
|
||||
final class WorkingTimeCalculatorNone implements WorkingTimeCalculator
|
||||
{
|
||||
public function getWorkHoursForDay(\DateTimeInterface $dateTime): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isWorkDay(\DateTimeInterface $dateTime): bool
|
||||
{
|
||||
// we don't know it, so we must assume every day is a a working day
|
||||
return true;
|
||||
}
|
||||
}
|
||||
48
src/WorkingTime/Mode/WorkingTimeMode.php
Normal file
48
src/WorkingTime/Mode/WorkingTimeMode.php
Normal 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\WorkingTime\Mode;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\Model\UserContractModel;
|
||||
use App\WorkingTime\Calculator\WorkingTimeCalculator;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
#[AutoconfigureTag]
|
||||
interface WorkingTimeMode
|
||||
{
|
||||
/**
|
||||
* Short and unique identifier for this mode.
|
||||
*/
|
||||
public function getId(): string;
|
||||
|
||||
/**
|
||||
* @return int<0, 100>
|
||||
*/
|
||||
public function getOrder(): int;
|
||||
|
||||
/**
|
||||
* Translation key for the name of this mode.
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface<UserContractModel> $builder
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void;
|
||||
|
||||
public function getCalculator(User $user): WorkingTimeCalculator;
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getFormFields(): array;
|
||||
}
|
||||
77
src/WorkingTime/Mode/WorkingTimeModeDay.php
Normal file
77
src/WorkingTime/Mode/WorkingTimeModeDay.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?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\WorkingTime\Mode;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\Type\DurationType;
|
||||
use App\WorkingTime\Calculator\WorkingTimeCalculator;
|
||||
use App\WorkingTime\Calculator\WorkingTimeCalculatorDay;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
|
||||
use Symfony\Component\Validator\Constraints\LessThanOrEqual;
|
||||
|
||||
class WorkingTimeModeDay implements WorkingTimeMode
|
||||
{
|
||||
final public const ID = 'day';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $fields = [
|
||||
'workHoursMonday' => 'Monday',
|
||||
'workHoursTuesday' => 'Tuesday',
|
||||
'workHoursWednesday' => 'Wednesday',
|
||||
'workHoursThursday' => 'Thursday',
|
||||
'workHoursFriday' => 'Friday',
|
||||
'workHoursSaturday' => 'Saturday',
|
||||
'workHoursSunday' => 'Sunday',
|
||||
];
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'hours_per_day';
|
||||
}
|
||||
|
||||
public function getFormFields(): array
|
||||
{
|
||||
return array_keys($this->fields);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$durationOptions = [
|
||||
'required' => false,
|
||||
'translation_domain' => 'system-configuration',
|
||||
'constraints' => [
|
||||
new GreaterThanOrEqual(0),
|
||||
new LessThanOrEqual(86400),
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($this->fields as $field => $label) {
|
||||
$builder->add($field, DurationType::class, array_merge(['label' => $label], $durationOptions));
|
||||
}
|
||||
}
|
||||
|
||||
public function getCalculator(User $user): WorkingTimeCalculator
|
||||
{
|
||||
return new WorkingTimeCalculatorDay($user);
|
||||
}
|
||||
}
|
||||
55
src/WorkingTime/Mode/WorkingTimeModeFactory.php
Normal file
55
src/WorkingTime/Mode/WorkingTimeModeFactory.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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\WorkingTime\Mode;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
|
||||
|
||||
final class WorkingTimeModeFactory
|
||||
{
|
||||
/**
|
||||
* @param iterable<WorkingTimeMode> $modes
|
||||
*/
|
||||
public function __construct(
|
||||
#[TaggedIterator(WorkingTimeMode::class)]
|
||||
private readonly iterable $modes
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WorkingTimeMode[]
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
$modes = [];
|
||||
foreach ($this->modes as $mode) {
|
||||
$modes[] = $mode;
|
||||
}
|
||||
|
||||
return $modes;
|
||||
}
|
||||
|
||||
public function getModeForUser(User $user): WorkingTimeMode
|
||||
{
|
||||
return $this->getMode($user->getWorkContractMode());
|
||||
}
|
||||
|
||||
public function getMode(string $contractMode): WorkingTimeMode
|
||||
{
|
||||
foreach ($this->modes as $mode) {
|
||||
if ($mode->getId() === $contractMode) {
|
||||
return $mode;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Unknown working contract mode: ' . $contractMode);
|
||||
}
|
||||
}
|
||||
50
src/WorkingTime/Mode/WorkingTimeModeNone.php
Normal file
50
src/WorkingTime/Mode/WorkingTimeModeNone.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\WorkingTime\Mode;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\WorkingTime\Calculator\WorkingTimeCalculator;
|
||||
use App\WorkingTime\Calculator\WorkingTimeCalculatorNone;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class WorkingTimeModeNone implements WorkingTimeMode
|
||||
{
|
||||
final public const ID = 'none';
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getFormFields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
public function getCalculator(User $user): WorkingTimeCalculator
|
||||
{
|
||||
return new WorkingTimeCalculatorNone();
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,15 @@ use App\Event\WorkingTimeYearSummaryEvent;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Repository\WorkingTimeRepository;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use App\WorkingTime\Mode\WorkingTimeMode;
|
||||
use App\WorkingTime\Mode\WorkingTimeModeFactory;
|
||||
use App\WorkingTime\Model\Month;
|
||||
use App\WorkingTime\Model\Year;
|
||||
use App\WorkingTime\Model\YearPerUserSummary;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* @internal this API and the entire namespace is instable: you should expect changes!
|
||||
* @internal this API and the entire namespace is experimental: expect changes!
|
||||
*/
|
||||
final class WorkingTimeService
|
||||
{
|
||||
@@ -33,11 +35,17 @@ final class WorkingTimeService
|
||||
public function __construct(
|
||||
private readonly TimesheetRepository $timesheetRepository,
|
||||
private readonly WorkingTimeRepository $workingTimeRepository,
|
||||
private readonly EventDispatcherInterface $eventDispatcher
|
||||
private readonly EventDispatcherInterface $eventDispatcher,
|
||||
private readonly WorkingTimeModeFactory $contractModeService
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function getContractMode(User $user): WorkingTimeMode
|
||||
{
|
||||
return $this->contractModeService->getModeForUser($user);
|
||||
}
|
||||
|
||||
public function getYearSummary(Year $year, \DateTimeInterface $until): YearPerUserSummary
|
||||
{
|
||||
$yearPerUserSummary = new YearPerUserSummary($year);
|
||||
@@ -94,6 +102,7 @@ final class WorkingTimeService
|
||||
|
||||
$stats = null;
|
||||
$firstDay = $user->getWorkStartingDay();
|
||||
$calculator = $this->getContractMode($user)->getCalculator($user);
|
||||
|
||||
foreach ($year->getMonths() as $month) {
|
||||
foreach ($month->getDays() as $day) {
|
||||
@@ -111,7 +120,7 @@ final class WorkingTimeService
|
||||
$result = new WorkingTime($user, $dayDate);
|
||||
|
||||
if ($firstDay === null || $firstDay <= $dayDate) {
|
||||
$result->setExpectedTime($user->getWorkHoursForDay($dayDate));
|
||||
$result->setExpectedTime($calculator->getWorkHoursForDay($dayDate));
|
||||
}
|
||||
|
||||
if (\array_key_exists($key, $stats)) {
|
||||
|
||||
Reference in New Issue
Block a user