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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user