Release 2.0.26 (#4087)

- setting "rounding days" not required
- developer: added user pref for public holiday group (to be used by plugins)
- developer: added WorkingTimeYearEvent (to be used by plugins)
- user pref: cleanup work contract form and support more fields (to be used by plugins)
- code cleanup
- bump theme and composer packages
This commit is contained in:
Kevin Papst
2023-06-09 16:29:19 +02:00
committed by GitHub
parent 0663995d06
commit 6e781b59e2
22 changed files with 241 additions and 167 deletions

View File

@@ -35,7 +35,7 @@
"friendsofsymfony/rest-bundle": "^3.0",
"gedmo/doctrine-extensions": "^3.6",
"jms/serializer-bundle": "^5.0",
"kevinpapst/tabler-bundle": "^0.20",
"kevinpapst/tabler-bundle": "^0.21",
"league/csv": "^9.4",
"mpdf/mpdf": "^8.0",
"nelmio/api-doc-bundle": "^4.0",

102
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "964ff2d76fe260ae39826660e6335625",
"content-hash": "ff7a790b502d29429fee1326938b0f69",
"packages": [
{
"name": "azuyalabs/yasumi",
@@ -821,16 +821,16 @@
},
{
"name": "doctrine/doctrine-bundle",
"version": "2.9.1",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/DoctrineBundle.git",
"reference": "7539b3c8bd620f7df6c2c6d510204bd2ce0064e3"
"reference": "b2ec6c2668f6dc514e8bf51257d19c7c19398afe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/7539b3c8bd620f7df6c2c6d510204bd2ce0064e3",
"reference": "7539b3c8bd620f7df6c2c6d510204bd2ce0064e3",
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/b2ec6c2668f6dc514e8bf51257d19c7c19398afe",
"reference": "b2ec6c2668f6dc514e8bf51257d19c7c19398afe",
"shasum": ""
},
"require": {
@@ -917,7 +917,7 @@
],
"support": {
"issues": "https://github.com/doctrine/DoctrineBundle/issues",
"source": "https://github.com/doctrine/DoctrineBundle/tree/2.9.1"
"source": "https://github.com/doctrine/DoctrineBundle/tree/2.10.0"
},
"funding": [
{
@@ -933,7 +933,7 @@
"type": "tidelift"
}
],
"time": "2023-04-14T05:39:34+00:00"
"time": "2023-06-05T14:43:41+00:00"
},
{
"name": "doctrine/doctrine-migrations-bundle",
@@ -1204,30 +1204,30 @@
},
{
"name": "doctrine/instantiator",
"version": "1.5.0",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
"reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
"php": "^8.1"
},
"require-dev": {
"doctrine/coding-standard": "^9 || ^11",
"doctrine/coding-standard": "^11",
"ext-pdo": "*",
"ext-phar": "*",
"phpbench/phpbench": "^0.16 || ^1",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^4.30 || ^5.4"
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "^1.9.4",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5.27",
"vimeo/psalm": "^5.4"
},
"type": "library",
"autoload": {
@@ -1254,7 +1254,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/1.5.0"
"source": "https://github.com/doctrine/instantiator/tree/2.0.0"
},
"funding": [
{
@@ -1270,7 +1270,7 @@
"type": "tidelift"
}
],
"time": "2022-12-30T00:15:36+00:00"
"time": "2022-12-30T00:23:10+00:00"
},
{
"name": "doctrine/lexer",
@@ -2238,21 +2238,21 @@
},
{
"name": "jms/serializer",
"version": "3.23.0",
"version": "3.24.0",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/serializer.git",
"reference": "ac0b16ee5317d1aacc41deb91c6c325eae97c176"
"reference": "bd18f0a6ee4d75515b33091afa3333e100e0be43"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/schmittjoh/serializer/zipball/ac0b16ee5317d1aacc41deb91c6c325eae97c176",
"reference": "ac0b16ee5317d1aacc41deb91c6c325eae97c176",
"url": "https://api.github.com/repos/schmittjoh/serializer/zipball/bd18f0a6ee4d75515b33091afa3333e100e0be43",
"reference": "bd18f0a6ee4d75515b33091afa3333e100e0be43",
"shasum": ""
},
"require": {
"doctrine/annotations": "^1.13 || ^2.0",
"doctrine/instantiator": "^1.0.3",
"doctrine/instantiator": "^1.0.3 || ^2.0",
"doctrine/lexer": "^1.1 || ^2",
"jms/metadata": "^2.6",
"php": "^7.2||^8.0",
@@ -2268,7 +2268,7 @@
"ocramius/proxy-manager": "^1.0|^2.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "^1.0.2",
"phpunit/phpunit": "^8.5.21||^9.0",
"phpunit/phpunit": "^8.5.21||^9.0||^10.0",
"psr/container": "^1.0|^2.0",
"symfony/dependency-injection": "^3.0|^4.0|^5.0|^6.0",
"symfony/expression-language": "^3.2|^4.0|^5.0|^6.0",
@@ -2322,7 +2322,7 @@
],
"support": {
"issues": "https://github.com/schmittjoh/serializer/issues",
"source": "https://github.com/schmittjoh/serializer/tree/3.23.0"
"source": "https://github.com/schmittjoh/serializer/tree/3.24.0"
},
"funding": [
{
@@ -2330,7 +2330,7 @@
"type": "github"
}
],
"time": "2023-02-17T17:40:48+00:00"
"time": "2023-06-04T20:28:00+00:00"
},
{
"name": "jms/serializer-bundle",
@@ -2421,16 +2421,16 @@
},
{
"name": "kevinpapst/tabler-bundle",
"version": "0.20",
"version": "0.21",
"source": {
"type": "git",
"url": "https://github.com/kevinpapst/TablerBundle.git",
"reference": "ed21fc9b1ef0183781799f1f21cca11ceb29eb47"
"reference": "64a291914df3b6088744c71b4584b75ecc4c541a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kevinpapst/TablerBundle/zipball/ed21fc9b1ef0183781799f1f21cca11ceb29eb47",
"reference": "ed21fc9b1ef0183781799f1f21cca11ceb29eb47",
"url": "https://api.github.com/repos/kevinpapst/TablerBundle/zipball/64a291914df3b6088744c71b4584b75ecc4c541a",
"reference": "64a291914df3b6088744c71b4584b75ecc4c541a",
"shasum": ""
},
"require": {
@@ -2479,7 +2479,7 @@
"description": "Admin/Backend theme bundle for Symfony based on Tabler.io",
"support": {
"issues": "https://github.com/kevinpapst/TablerBundle/issues",
"source": "https://github.com/kevinpapst/TablerBundle/tree/0.20"
"source": "https://github.com/kevinpapst/TablerBundle/tree/0.21"
},
"funding": [
{
@@ -2491,7 +2491,7 @@
"type": "github"
}
],
"time": "2023-05-30T16:33:49+00:00"
"time": "2023-06-06T21:31:13+00:00"
},
{
"name": "laminas/laminas-escaper",
@@ -9769,16 +9769,16 @@
},
{
"name": "twig/extra-bundle",
"version": "v3.6.0",
"version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/twig-extra-bundle.git",
"reference": "4a9674e775f49a9df5e26da66546e8f3364afe67"
"reference": "802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/4a9674e775f49a9df5e26da66546e8f3364afe67",
"reference": "4a9674e775f49a9df5e26da66546e8f3364afe67",
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49",
"reference": "802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49",
"shasum": ""
},
"require": {
@@ -9827,7 +9827,7 @@
"twig"
],
"support": {
"source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.6.0"
"source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.6.1"
},
"funding": [
{
@@ -9839,7 +9839,7 @@
"type": "tidelift"
}
],
"time": "2023-04-14T11:03:02+00:00"
"time": "2023-05-06T11:11:46+00:00"
},
{
"name": "twig/inky-extra",
@@ -10040,16 +10040,16 @@
},
{
"name": "twig/twig",
"version": "v3.6.0",
"version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "106c170d08e8415d78be2d16c3d057d0d108262b"
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/106c170d08e8415d78be2d16c3d057d0d108262b",
"reference": "106c170d08e8415d78be2d16c3d057d0d108262b",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
"shasum": ""
},
"require": {
@@ -10095,7 +10095,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.6.0"
"source": "https://github.com/twigphp/Twig/tree/v3.6.1"
},
"funding": [
{
@@ -10107,7 +10107,7 @@
"type": "tidelift"
}
],
"time": "2023-05-03T19:06:57+00:00"
"time": "2023-06-08T12:52:13+00:00"
},
{
"name": "webmozart/assert",
@@ -11115,16 +11115,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.15",
"version": "1.10.18",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd"
"reference": "52b6416c579663eebdd2f1d97df21971daf3b43f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd",
"reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/52b6416c579663eebdd2f1d97df21971daf3b43f",
"reference": "52b6416c579663eebdd2f1d97df21971daf3b43f",
"shasum": ""
},
"require": {
@@ -11173,7 +11173,7 @@
"type": "tidelift"
}
],
"time": "2023-05-09T15:28:01+00:00"
"time": "2023-06-07T22:00:43+00:00"
},
{
"name": "phpstan/phpstan-doctrine",

View File

@@ -1141,16 +1141,6 @@ parameters:
count: 1
path: src/Controller/PluginController.php
-
message: "#^PHPDoc tag @var for variable \\$iterator contains generic class ArrayIterator but does not specify its types\\: TKey, TValue$#"
count: 1
path: src/Controller/ProfileController.php
-
message: "#^Parameter \\#1 \\$callback of method ArrayIterator\\<\\(int\\|string\\),mixed\\>\\:\\:uasort\\(\\) expects callable\\(mixed, mixed\\)\\: int, Closure\\(App\\\\Entity\\\\UserPreference, App\\\\Entity\\\\UserPreference\\)\\: \\-1\\|1 given\\.$#"
count: 1
path: src/Controller/ProfileController.php
-
message: "#^Cannot call method getCurrency\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
count: 1
@@ -1926,26 +1916,6 @@ parameters:
count: 1
path: src/Entity/Timesheet.php
-
message: "#^Method App\\\\Entity\\\\Timesheet\\:\\:getTagsAsArray\\(\\) should return array\\<string\\> but returns array\\<string\\|null\\>\\.$#"
count: 1
path: src/Entity/Timesheet.php
-
message: "#^Method App\\\\Entity\\\\Timesheet\\:\\:localizeDates\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Timesheet.php
-
message: "#^Method App\\\\Entity\\\\Timesheet\\:\\:removeTag\\(\\) has no return type specified\\.$#"
count: 1
path: src/Entity/Timesheet.php
-
message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(App\\\\Entity\\\\Tag\\)\\: string\\|null given\\.$#"
count: 1
path: src/Entity/Timesheet.php
-
message: "#^Parameter \\#1 \\$name of method App\\\\Entity\\\\Timesheet\\:\\:getMetaField\\(\\) expects string, string\\|null given\\.$#"
count: 1
@@ -5221,26 +5191,11 @@ parameters:
count: 1
path: src/Repository/Loader/TimesheetLoader.php
-
message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(App\\\\Entity\\\\Project\\)\\: int\\|null given\\.$#"
count: 1
path: src/Repository/Loader/TimesheetLoader.php
-
message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(App\\\\Entity\\\\Timesheet\\)\\: int\\|null given\\.$#"
count: 2
path: src/Repository/Loader/TimesheetLoader.php
-
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\Timesheet\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\TimesheetLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
count: 1
path: src/Repository/Loader/TimesheetLoader.php
-
message: "#^Parameter \\#2 \\$array of function array_map expects array, mixed given\\.$#"
count: 3
path: src/Repository/Loader/TimesheetLoader.php
-
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\User\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\UserLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
count: 1

View File

@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '2.0.25';
public const VERSION = '2.0.26';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 20025;
public const VERSION_ID = 20026;
/**
* The software name
*/

View File

@@ -271,9 +271,9 @@ final class ProfileController extends AbstractController
// prepare ordered preferences
$sections = [];
/** @var \ArrayIterator $iterator */
/** @var \ArrayIterator<int, UserPreference> $iterator */
$iterator = $profile->getPreferences()->getIterator();
$iterator->uasort(function (UserPreference $a, UserPreference $b) {
$iterator->uasort(function ($a, $b) {
return ($a->getOrder() < $b->getOrder()) ? -1 : 1;
});

View File

@@ -422,6 +422,7 @@ final class SystemConfigurationController extends AbstractController
]),
(new Configuration('timesheet.rounding.default.days'))
->setType(WeekDaysType::class)
->setRequired(false)
->setTranslationDomain('system-configuration'),
]),
(new SystemConfigurationModel('invoice'))

View File

@@ -239,7 +239,7 @@ class Timesheet implements EntityWithMetaFields, ExportableItem
* Make sure begin and end date have the correct timezone.
* This will be called once for each item after being loaded from the database.
*/
protected function localizeDates()
protected function localizeDates(): void
{
if ($this->localized) {
return;
@@ -432,7 +432,7 @@ class Timesheet implements EntityWithMetaFields, ExportableItem
/**
* @param Tag $tag
*/
public function removeTag(Tag $tag)
public function removeTag(Tag $tag): void
{
if (!$this->tags->contains($tag)) {
return;
@@ -453,11 +453,14 @@ class Timesheet implements EntityWithMetaFields, ExportableItem
*/
public function getTagsAsArray(): array
{
/** @var array<Tag> $tags */
$tags = $this->getTags()->toArray();
return array_map(
function (Tag $element) {
return $element->getName();
function ($element) {
return (string) $element->getName();
},
$this->getTags()->toArray()
$tags
);
}

View File

@@ -1174,6 +1174,13 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return (int) $this->getPreferenceValue(UserPreference::WORK_HOURS_SUNDAY, 0);
}
public function getPublicHolidayGroup(): null|string
{
$group = $this->getPreferenceValue(UserPreference::PUBLIC_HOLIDAY_GROUP);
return $group === null ? $group : (string) $group;
}
public function getHolidaysPerYear(): int
{
return (int) $this->getPreferenceValue(UserPreference::HOLIDAYS_PER_YEAR, 0);
@@ -1214,6 +1221,11 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
$this->setPreferenceValue(UserPreference::WORK_HOURS_SUNDAY, $seconds);
}
public function setPublicHolidayGroup(null|string $group = null): void
{
$this->setPreferenceValue(UserPreference::PUBLIC_HOLIDAY_GROUP, $group);
}
public function setHolidaysPerYear(int $holidays): void
{
$this->setPreferenceValue(UserPreference::HOLIDAYS_PER_YEAR, $holidays);

View File

@@ -38,6 +38,7 @@ class UserPreference
public const WORK_HOURS_FRIDAY = 'work_friday';
public const WORK_HOURS_SATURDAY = 'work_saturday';
public const WORK_HOURS_SUNDAY = 'work_sunday';
public const PUBLIC_HOLIDAY_GROUP = 'public_holiday_group';
public const HOLIDAYS_PER_YEAR = 'holidays';
#[ORM\Id]

View File

@@ -0,0 +1,25 @@
<?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\WorkingTime\Model\Year;
use Symfony\Contracts\EventDispatcher\Event;
final class WorkingTimeYearEvent extends Event
{
public function __construct(private Year $year)
{
}
public function getYear(): Year
{
return $this->year;
}
}

View File

@@ -12,11 +12,9 @@ namespace App\Form;
use App\Entity\User;
use App\Form\Type\DurationType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
use Symfony\Component\Validator\Constraints\Range;
/**
* @extends AbstractType<User>
@@ -40,14 +38,6 @@ final class UserContractType extends AbstractType
->add('workHoursFriday', DurationType::class, array_merge(['label' => 'Friday'], $dayOptions))
->add('workHoursSaturday', DurationType::class, array_merge(['label' => 'Saturday'], $dayOptions))
->add('workHoursSunday', DurationType::class, array_merge(['label' => 'Sunday'], $dayOptions))
/*
->add('holidaysPerYear', IntegerType::class, [
'label' => false,
'constraints' => [
new Range(['min' => 0, 'max' => 365])
],
])
*/
;
}

View File

@@ -44,6 +44,11 @@ class Month
$this->days['_' . $day->getDay()->format('d')] = $day;
}
public function getDay(DateTimeInterface $date): Day
{
return $this->days['_' . $date->format('d')];
}
/**
* @return Day[]
*/

View File

@@ -50,6 +50,11 @@ class Year
return $this->months['_' . $month->format('m')];
}
public function getDay(\DateTimeInterface $date): Day
{
return $this->getMonth($date)->getDay($date);
}
/**
* @return Month[]
*/

View File

@@ -44,6 +44,7 @@ final class TimesheetLoader implements LoaderInterface
$em = $this->entityManager;
$qb = $em->createQueryBuilder();
/** @var array<Timesheet> $timesheets */
$timesheets = $qb->select('PARTIAL t.{id}', 'project')
->from(Timesheet::class, 't')
->leftJoin('t.project', 'project')
@@ -51,7 +52,7 @@ final class TimesheetLoader implements LoaderInterface
->getQuery()
->execute();
$projectIds = array_map(function (Timesheet $timesheet) {
$projectIds = array_map(function ($timesheet) {
return $timesheet->getProject()->getId();
}, $timesheets);
@@ -66,6 +67,7 @@ final class TimesheetLoader implements LoaderInterface
}
$qb = $em->createQueryBuilder();
/** @var array<Project> $projects */
$projects = $qb->select('PARTIAL p.{id}', 'customer')
->from(Project::class, 'p')
->leftJoin('p.customer', 'customer')
@@ -74,7 +76,7 @@ final class TimesheetLoader implements LoaderInterface
->execute();
if ($this->fullyHydrated) {
$customerIds = array_map(function (Project $project) {
$customerIds = array_map(function ($project) {
return $project->getCustomer()->getId();
}, $projects);

View File

@@ -15,6 +15,17 @@ use App\Model\Day as BaseDay;
final class Day extends BaseDay
{
private ?WorkingTime $workingTime = null;
/** @var array<string, int> */
private array $descriptions = [];
public function isLocked(): bool
{
if ($this->workingTime !== null && $this->workingTime->isApproved()) {
return true;
}
return false;
}
public function getWorkingTime(): ?WorkingTime
{
@@ -25,4 +36,20 @@ final class Day extends BaseDay
{
$this->workingTime = $workingTime;
}
/**
* @return array<string, int>
*/
public function getDescriptions(): array
{
return $this->descriptions;
}
/**
* Descriptions show up in the approval PDF and maybe in other places as well.
*/
public function addDescription(string $description, int $duration): void
{
$this->descriptions[$description] = $duration;
}
}

View File

@@ -14,11 +14,10 @@ use App\Model\Month as BaseMonth;
/**
* @method array<Day> getDays()
* @method Day getDay(\DateTimeInterface $date)
*/
final class Month extends BaseMonth
{
private ?bool $locked = null;
/**
* A month is only locked IF every day is approved.
* If there is even one day left open, the entire month is not locked.
@@ -27,16 +26,13 @@ final class Month extends BaseMonth
*/
public function isLocked(): bool
{
if ($this->locked === null) {
$this->locked = true;
foreach ($this->getDays() as $day) {
if ($day->getWorkingTime() !== null && !$day->getWorkingTime()->isApproved()) {
$this->locked = false;
}
foreach ($this->getDays() as $day) {
if (!$day->isLocked()) {
return false;
}
}
return $this->locked;
return true;
}
public function getLockDate(): ?\DateTimeInterface

View File

@@ -11,6 +11,7 @@ namespace App\WorkingTime;
use App\Entity\User;
use App\Entity\WorkingTime;
use App\Event\WorkingTimeYearEvent;
use App\Event\WorkingTimeYearSummaryEvent;
use App\Repository\TimesheetRepository;
use App\Repository\WorkingTimeRepository;
@@ -79,12 +80,15 @@ final class WorkingTimeService
}
}
$event = new WorkingTimeYearEvent($year);
$this->eventDispatcher->dispatch($event);
return $year;
}
public function getMonth(User $user, \DateTimeInterface $monthDate): Month
{
// TODO improve me, do not calculate the entire year for that
// uses the year, because that triggers the required events to collect all different working times
$year = $this->getYear($user, $monthDate);
return $year->getMonth($monthDate);

View File

@@ -2,40 +2,35 @@
{% block form_content %}
<h3>{{ 'work_times_should'|trans }}</h3>
<table class="table">
<thead>
<tr>
<th>{{ 'Monday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Tuesday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Wednesday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Thursday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Friday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Saturday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Sunday'|trans({}, 'system-configuration') }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ form_widget(form.workHoursMonday) }}</td>
<td>{{ form_widget(form.workHoursTuesday) }}</td>
<td>{{ form_widget(form.workHoursWednesday) }}</td>
<td>{{ form_widget(form.workHoursThursday) }}</td>
<td>{{ form_widget(form.workHoursFriday) }}</td>
<td>{{ form_widget(form.workHoursSaturday) }}</td>
<td>{{ form_widget(form.workHoursSunday) }}</td>
</tr>
</tbody>
</table>
<p>{{ 'work_times_should.help'|trans }}</p>
{% if form.holidaysPerYear is defined %}
<h3>{{ 'pto_year_holiday'|trans }}</h3>
<div class="mb-3">
<label class="form-label">{{ 'work_times_should'|trans }}</label>
<table class="table table-borderless">
<thead>
<tr>
<th>{{ 'Monday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Tuesday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Wednesday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Thursday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Friday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Saturday'|trans({}, 'system-configuration') }}</th>
<th>{{ 'Sunday'|trans({}, 'system-configuration') }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ form_widget(form.workHoursMonday) }}</td>
<td>{{ form_widget(form.workHoursTuesday) }}</td>
<td>{{ form_widget(form.workHoursWednesday) }}</td>
<td>{{ form_widget(form.workHoursThursday) }}</td>
<td>{{ form_widget(form.workHoursFriday) }}</td>
<td>{{ form_widget(form.workHoursSaturday) }}</td>
<td>{{ form_widget(form.workHoursSunday) }}</td>
</tr>
</tbody>
</table>
<div class="form-text mb-0 help-text">{{ 'work_times_should.help'|trans }}</div>
</div>
{{ form_rest(form) }}
{% endif %}
{% endblock %}

View File

@@ -82,6 +82,7 @@ class UserTest extends TestCase
self::assertEquals(0, $user->getWorkHoursSunday());
self::assertEquals(0, $user->getHolidaysPerYear());
self::assertFalse($user->hasWorkHourConfiguration());
self::assertNull($user->getPublicHolidayGroup());
}
public function testWorkContract(): void
@@ -115,6 +116,12 @@ class UserTest extends TestCase
self::assertEquals(7600, $user->getWorkHoursForDay(new \DateTime('2023-05-12 12:00:00', new \DateTimeZone('Europe/Berlin'))));
self::assertEquals(7700, $user->getWorkHoursForDay(new \DateTime('2023-05-13 12:00:00', new \DateTimeZone('Europe/Berlin'))));
self::assertEquals(7800, $user->getWorkHoursForDay(new \DateTime('2023-05-14 12:00:00', new \DateTimeZone('Europe/Berlin'))));
$user->setPublicHolidayGroup('10');
self::assertEquals('10', $user->getPublicHolidayGroup());
$user->setPublicHolidayGroup('DE-NRW');
self::assertEquals('DE-NRW', $user->getPublicHolidayGroup());
}
public function testColor(): void

View File

@@ -0,0 +1,31 @@
<?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\Tests\Event;
use App\Entity\User;
use App\Event\WorkingTimeYearEvent;
use App\WorkingTime\Model\Year;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Event\WorkingTimeYearEvent
*/
class WorkingTimeYearEventTest extends TestCase
{
public function testGetter(): void
{
$user = new User();
$date = new \DateTime('2023-02-10');
$year = new Year($date, $user);
$sut = new WorkingTimeYearEvent($year);
self::assertSame($year, $sut->getYear());
}
}

View File

@@ -30,7 +30,7 @@ class MonthTest extends TestCase
$month = new Month($date);
self::assertEquals(sprintf('2020-%s-25', $monthKey), $month->getMonth()->format('Y-m-d'));
self::assertCount($days, $month->getDays());
self::assertTrue($month->isLocked());
self::assertFalse($month->isLocked());
self::assertEquals(0, $month->getActualTime());
self::assertEquals(0, $month->getExpectedTime(new \DateTimeImmutable('2035-01-02')));
self::assertNull($month->getLockDate());
@@ -51,6 +51,26 @@ class MonthTest extends TestCase
self::assertEquals($expected, $day->getWorkingTime()->getExpectedTime());
self::assertEquals($duration, $day->getWorkingTime()->getActualTime());
}
self::assertFalse($month->isLocked());
foreach ($month->getDays() as $day) {
$workingTime = $day->getWorkingTime();
self::assertNotNull($workingTime);
$workingTime->setApprovedAt(new \DateTimeImmutable());
}
self::assertTrue($month->isLocked());
$day = $month->getDays()[5];
$wt = $day->getWorkingTime();
self::assertNotNull($wt);
$day->setWorkingTime(null);
self::assertFalse($month->isLocked());
$day->setWorkingTime($wt);
self::assertTrue($month->isLocked());
$wt->setApprovedAt(null);
self::assertFalse($month->isLocked());
}
}
}

View File

@@ -6582,11 +6582,6 @@ parameters:
count: 1
path: Form/Type/TypeTestModel.php
-
message: "#^Method App\\\\Tests\\\\Form\\\\Type\\\\TypeTestModel\\:\\:__set\\(\\) has no return type specified\\.$#"
count: 1
path: Form/Type/TypeTestModel.php
-
message: "#^Method App\\\\Tests\\\\Form\\\\Type\\\\TypeTestModel\\:\\:__set\\(\\) has parameter \\$name with no type specified\\.$#"
count: 1