From 6e781b59e2d4b9f32803f95014eb629b9c9204cb Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Fri, 9 Jun 2023 16:29:19 +0200 Subject: [PATCH] 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 --- composer.json | 2 +- composer.lock | 102 +++++++++--------- phpstan.neon | 45 -------- src/Constants.php | 4 +- src/Controller/ProfileController.php | 4 +- .../SystemConfigurationController.php | 1 + src/Entity/Timesheet.php | 13 ++- src/Entity/User.php | 12 +++ src/Entity/UserPreference.php | 1 + src/Event/WorkingTimeYearEvent.php | 25 +++++ src/Form/UserContractType.php | 10 -- src/Model/Month.php | 5 + src/Model/Year.php | 5 + src/Repository/Loader/TimesheetLoader.php | 6 +- src/WorkingTime/Model/Day.php | 27 +++++ src/WorkingTime/Model/Month.php | 14 +-- src/WorkingTime/WorkingTimeService.php | 6 +- templates/user/contract.html.twig | 61 +++++------ tests/Entity/UserTest.php | 7 ++ tests/Event/WorkingTimeYearEventTest.php | 31 ++++++ tests/WorkingTime/Model/MonthTest.php | 22 +++- tests/phpstan.neon | 5 - 22 files changed, 241 insertions(+), 167 deletions(-) create mode 100644 src/Event/WorkingTimeYearEvent.php create mode 100644 tests/Event/WorkingTimeYearEventTest.php diff --git a/composer.json b/composer.json index 6fd224df..78712efc 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index 4d6d89d3..b2305f8f 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/phpstan.neon b/phpstan.neon index 301b3fac..9132d3f9 100644 --- a/phpstan.neon +++ b/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\\ but returns array\\\\.$#" - 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\\\\) 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\\\\) of method App\\\\Repository\\\\Loader\\\\UserLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#" count: 1 diff --git a/src/Constants.php b/src/Constants.php index ba888861..ddc227b1 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -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 */ diff --git a/src/Controller/ProfileController.php b/src/Controller/ProfileController.php index 6e8d9229..6dffa27e 100644 --- a/src/Controller/ProfileController.php +++ b/src/Controller/ProfileController.php @@ -271,9 +271,9 @@ final class ProfileController extends AbstractController // prepare ordered preferences $sections = []; - /** @var \ArrayIterator $iterator */ + /** @var \ArrayIterator $iterator */ $iterator = $profile->getPreferences()->getIterator(); - $iterator->uasort(function (UserPreference $a, UserPreference $b) { + $iterator->uasort(function ($a, $b) { return ($a->getOrder() < $b->getOrder()) ? -1 : 1; }); diff --git a/src/Controller/SystemConfigurationController.php b/src/Controller/SystemConfigurationController.php index 63dc0f91..05c4c693 100644 --- a/src/Controller/SystemConfigurationController.php +++ b/src/Controller/SystemConfigurationController.php @@ -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')) diff --git a/src/Entity/Timesheet.php b/src/Entity/Timesheet.php index b14010c6..ed5d93f0 100644 --- a/src/Entity/Timesheet.php +++ b/src/Entity/Timesheet.php @@ -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 $tags */ + $tags = $this->getTags()->toArray(); + return array_map( - function (Tag $element) { - return $element->getName(); + function ($element) { + return (string) $element->getName(); }, - $this->getTags()->toArray() + $tags ); } diff --git a/src/Entity/User.php b/src/Entity/User.php index a0ce98f5..018c05ac 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -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); diff --git a/src/Entity/UserPreference.php b/src/Entity/UserPreference.php index 36e1841c..d7d057da 100644 --- a/src/Entity/UserPreference.php +++ b/src/Entity/UserPreference.php @@ -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] diff --git a/src/Event/WorkingTimeYearEvent.php b/src/Event/WorkingTimeYearEvent.php new file mode 100644 index 00000000..9d264266 --- /dev/null +++ b/src/Event/WorkingTimeYearEvent.php @@ -0,0 +1,25 @@ +year; + } +} diff --git a/src/Form/UserContractType.php b/src/Form/UserContractType.php index 65f6d1dc..4ae227f4 100644 --- a/src/Form/UserContractType.php +++ b/src/Form/UserContractType.php @@ -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 @@ -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]) - ], - ]) - */ ; } diff --git a/src/Model/Month.php b/src/Model/Month.php index 06fa8d4d..4805c018 100644 --- a/src/Model/Month.php +++ b/src/Model/Month.php @@ -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[] */ diff --git a/src/Model/Year.php b/src/Model/Year.php index d9900c67..414c4d98 100644 --- a/src/Model/Year.php +++ b/src/Model/Year.php @@ -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[] */ diff --git a/src/Repository/Loader/TimesheetLoader.php b/src/Repository/Loader/TimesheetLoader.php index f7346580..52a67c22 100644 --- a/src/Repository/Loader/TimesheetLoader.php +++ b/src/Repository/Loader/TimesheetLoader.php @@ -44,6 +44,7 @@ final class TimesheetLoader implements LoaderInterface $em = $this->entityManager; $qb = $em->createQueryBuilder(); + /** @var array $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 $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); diff --git a/src/WorkingTime/Model/Day.php b/src/WorkingTime/Model/Day.php index e6ecedb8..6cc5c681 100644 --- a/src/WorkingTime/Model/Day.php +++ b/src/WorkingTime/Model/Day.php @@ -15,6 +15,17 @@ use App\Model\Day as BaseDay; final class Day extends BaseDay { private ?WorkingTime $workingTime = null; + /** @var array */ + 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 + */ + 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; + } } diff --git a/src/WorkingTime/Model/Month.php b/src/WorkingTime/Model/Month.php index c1a415cc..f99e95a6 100644 --- a/src/WorkingTime/Model/Month.php +++ b/src/WorkingTime/Model/Month.php @@ -14,11 +14,10 @@ use App\Model\Month as BaseMonth; /** * @method array 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 diff --git a/src/WorkingTime/WorkingTimeService.php b/src/WorkingTime/WorkingTimeService.php index 93d5641e..514013b0 100644 --- a/src/WorkingTime/WorkingTimeService.php +++ b/src/WorkingTime/WorkingTimeService.php @@ -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); diff --git a/templates/user/contract.html.twig b/templates/user/contract.html.twig index d586e7bd..776b7f19 100644 --- a/templates/user/contract.html.twig +++ b/templates/user/contract.html.twig @@ -2,40 +2,35 @@ {% block form_content %} -

{{ 'work_times_should'|trans }}

- - - - - - - - - - - - - - - - - - - - - - - - -
{{ 'Monday'|trans({}, 'system-configuration') }}{{ 'Tuesday'|trans({}, 'system-configuration') }}{{ 'Wednesday'|trans({}, 'system-configuration') }}{{ 'Thursday'|trans({}, 'system-configuration') }}{{ 'Friday'|trans({}, 'system-configuration') }}{{ 'Saturday'|trans({}, 'system-configuration') }}{{ 'Sunday'|trans({}, 'system-configuration') }}
{{ form_widget(form.workHoursMonday) }}{{ form_widget(form.workHoursTuesday) }}{{ form_widget(form.workHoursWednesday) }}{{ form_widget(form.workHoursThursday) }}{{ form_widget(form.workHoursFriday) }}{{ form_widget(form.workHoursSaturday) }}{{ form_widget(form.workHoursSunday) }}
- -

{{ 'work_times_should.help'|trans }}

- - {% if form.holidaysPerYear is defined %} -

{{ 'pto_year_holiday'|trans }}

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'Monday'|trans({}, 'system-configuration') }}{{ 'Tuesday'|trans({}, 'system-configuration') }}{{ 'Wednesday'|trans({}, 'system-configuration') }}{{ 'Thursday'|trans({}, 'system-configuration') }}{{ 'Friday'|trans({}, 'system-configuration') }}{{ 'Saturday'|trans({}, 'system-configuration') }}{{ 'Sunday'|trans({}, 'system-configuration') }}
{{ form_widget(form.workHoursMonday) }}{{ form_widget(form.workHoursTuesday) }}{{ form_widget(form.workHoursWednesday) }}{{ form_widget(form.workHoursThursday) }}{{ form_widget(form.workHoursFriday) }}{{ form_widget(form.workHoursSaturday) }}{{ form_widget(form.workHoursSunday) }}
+
{{ 'work_times_should.help'|trans }}
+
{{ form_rest(form) }} - {% endif %} - {% endblock %} diff --git a/tests/Entity/UserTest.php b/tests/Entity/UserTest.php index 8a1c2bbf..6862861e 100644 --- a/tests/Entity/UserTest.php +++ b/tests/Entity/UserTest.php @@ -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 diff --git a/tests/Event/WorkingTimeYearEventTest.php b/tests/Event/WorkingTimeYearEventTest.php new file mode 100644 index 00000000..af3cccad --- /dev/null +++ b/tests/Event/WorkingTimeYearEventTest.php @@ -0,0 +1,31 @@ +getYear()); + } +} diff --git a/tests/WorkingTime/Model/MonthTest.php b/tests/WorkingTime/Model/MonthTest.php index d8fce323..bfa17632 100644 --- a/tests/WorkingTime/Model/MonthTest.php +++ b/tests/WorkingTime/Model/MonthTest.php @@ -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()); } } } diff --git a/tests/phpstan.neon b/tests/phpstan.neon index 69ab16de..efedc6ac 100644 --- a/tests/phpstan.neon +++ b/tests/phpstan.neon @@ -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