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

@@ -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);