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:
@@ -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
102
composer.lock
generated
@@ -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",
|
||||
|
||||
45
phpstan.neon
45
phpstan.neon
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
25
src/Event/WorkingTimeYearEvent.php
Normal file
25
src/Event/WorkingTimeYearEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
],
|
||||
])
|
||||
*/
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -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[]
|
||||
*/
|
||||
|
||||
@@ -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[]
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
|
||||
|
||||
31
tests/Event/WorkingTimeYearEventTest.php
Normal file
31
tests/Event/WorkingTimeYearEventTest.php
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user