Release 2.0.28 (#4172)

See https://github.com/kimai/kimai/pull/4172
This commit is contained in:
Kevin Papst
2023-07-09 15:27:27 +02:00
committed by GitHub
parent 651f812da8
commit 385753fbc3
27 changed files with 182 additions and 53 deletions

View File

@@ -606,7 +606,7 @@ export default class KimaiCalendar {
<li>` + this.options['translations']['activity'] + `: ` + escaper.escapeForHtml(eventObj.activity) + `</li> <li>` + this.options['translations']['activity'] + `: ` + escaper.escapeForHtml(eventObj.activity) + `</li>
</ul>` + </ul>` +
(eventObj.description !== null || eventObj.tags.length > 0 ? '<hr>' : '') + (eventObj.description !== null || eventObj.tags.length > 0 ? '<hr>' : '') +
(eventObj.description ? '<p>' + escaper.escapeForHtml(eventObj.description) + '</p>' : '') + tags + ` (eventObj.description ? '<div>' + escaper.escapeForHtml(eventObj.description) + '</div>' : '') + tags + `
</div>`; </div>`;
} }

View File

@@ -3,6 +3,9 @@
padding: 0; padding: 0;
list-style-type: none; list-style-type: none;
} }
div {
white-space: pre-line;
}
} }
.draggable { .draggable {

View File

@@ -78,6 +78,7 @@ tabler:
filter: fas fa-filter filter: fas fa-filter
help: far fa-question-circle help: far fa-question-circle
home: fas fa-home home: fas fa-home
info: fas fa-info-circle
import: fas fa-file-import import: fas fa-file-import
invoice: fas fa-file-contract invoice: fas fa-file-contract
invoice-template: fas fa-file-signature invoice-template: fas fa-file-signature
@@ -99,6 +100,7 @@ tabler:
password: fas fa-key password: fas fa-key
pause: fas fa-pause pause: fas fa-pause
pause-small: far fa-pause-circle pause-small: far fa-pause-circle
pending: fas fa-hourglass-half
pin: fas fa-thumbtack pin: fas fa-thumbtack
pdf: fas fa-file-pdf pdf: fas fa-file-pdf
permissions: fas fa-user-lock permissions: fas fa-user-lock
@@ -112,6 +114,7 @@ tabler:
reporting: far fa-chart-bar reporting: far fa-chart-bar
report: far fa-chart-bar report: far fa-chart-bar
review: fas fa-user-check review: fas fa-user-check
rejected: fas fa-ban
right: fas fa-chevron-right right: fas fa-chevron-right
right2: fas fa-angle-double-right right2: fas fa-angle-double-right
roles: fas fa-user-shield roles: fas fa-user-shield

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
"/build/app.99c7b6b4.js" "/build/app.99c7b6b4.js"
], ],
"css": [ "css": [
"/build/app.a61c91dc.css" "/build/app.f82609fb.css"
] ]
}, },
"export-pdf": { "export-pdf": {
@@ -45,7 +45,7 @@
"calendar": { "calendar": {
"js": [ "js": [
"/build/runtime.f0079159.js", "/build/runtime.f0079159.js",
"/build/calendar.25b368b9.js" "/build/calendar.e7b6fa49.js"
], ],
"css": [ "css": [
"/build/calendar.bb473428.css" "/build/calendar.bb473428.css"
@@ -64,7 +64,7 @@
"integrity": { "integrity": {
"/build/runtime.f0079159.js": "sha384-H22sAW1aTvyIPqvHOvGXWSWTxf0y6mptp+MsVmyXCfjx/WJjBbhX9gbUZ+qIuihV", "/build/runtime.f0079159.js": "sha384-H22sAW1aTvyIPqvHOvGXWSWTxf0y6mptp+MsVmyXCfjx/WJjBbhX9gbUZ+qIuihV",
"/build/app.99c7b6b4.js": "sha384-bPAdOMl3I6Ia3clnHgDiwoNKKe/bKnAdHUJdl7BXbsFNMg2wHsUDrGH+uNCQowqM", "/build/app.99c7b6b4.js": "sha384-bPAdOMl3I6Ia3clnHgDiwoNKKe/bKnAdHUJdl7BXbsFNMg2wHsUDrGH+uNCQowqM",
"/build/app.a61c91dc.css": "sha384-7rtbsyyUucsBktCZZXFuqnSHX0cvCWQ0KQgHpolGcWlFhaIEPn+ipnOc5yySeYwT", "/build/app.f82609fb.css": "sha384-X848pib0qXUB72LdaVSupYXnYPE/lNXHXNkJdlh4FRj534BVHyupi+ly3cfCkDRS",
"/build/export-pdf.d367a32e.js": "sha384-Z5baqnzjI636nYFs4g63ViIKBZKRW4Jhv/7PQmTEQlqhfA7eK0vUMUtiyy0R5A9u", "/build/export-pdf.d367a32e.js": "sha384-Z5baqnzjI636nYFs4g63ViIKBZKRW4Jhv/7PQmTEQlqhfA7eK0vUMUtiyy0R5A9u",
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg", "/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
"/build/invoice.2604495e.js": "sha384-D6JvhGSqlx7z72b/qD3nF3QDXPy+XsCSRGtWfs1icjDKOcd2UzuXwuSa/E1Fg2TJ", "/build/invoice.2604495e.js": "sha384-D6JvhGSqlx7z72b/qD3nF3QDXPy+XsCSRGtWfs1icjDKOcd2UzuXwuSa/E1Fg2TJ",
@@ -72,7 +72,7 @@
"/build/invoice-pdf.ce9aace0.js": "sha384-mtZkXrpsXFMX2YvmmTlCm91QgsTlZLzLJnDAFcTuBDtDZ7TNkqLYkfCeLzFLVfwb", "/build/invoice-pdf.ce9aace0.js": "sha384-mtZkXrpsXFMX2YvmmTlCm91QgsTlZLzLJnDAFcTuBDtDZ7TNkqLYkfCeLzFLVfwb",
"/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7", "/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7",
"/build/chart.f5becfac.js": "sha384-GSqETm8wULiVXyizvwRompfwu63r/C0Qd/AvrHDE4cqAKiIGCssb3QyBtGu1WN+W", "/build/chart.f5becfac.js": "sha384-GSqETm8wULiVXyizvwRompfwu63r/C0Qd/AvrHDE4cqAKiIGCssb3QyBtGu1WN+W",
"/build/calendar.25b368b9.js": "sha384-jKqj+OqQVIm8TXCEmnRveqG3348yVwvlb0k88kRn90DZGzbthjToMHUb5pwL/TtI", "/build/calendar.e7b6fa49.js": "sha384-/lbV1KHFBLr1Y/aaY7Pfckynm/EAcZqBwJQdxum3gvbDVdcthoxPSNTvf++vKadj",
"/build/calendar.bb473428.css": "sha384-900W9o8666Qpw21rLP7hn5Ql8tWd40k0IcE3QpFm7E7V6bKva7xQgGlyzVuuh4GK", "/build/calendar.bb473428.css": "sha384-900W9o8666Qpw21rLP7hn5Ql8tWd40k0IcE3QpFm7E7V6bKva7xQgGlyzVuuh4GK",
"/build/dashboard.6774a712.js": "sha384-lBwkNqUPv+IBbznagiFakY2BOIueq/Bg89wHO2XeM9YAZ51rPkrvdMd+XmFupHsM", "/build/dashboard.6774a712.js": "sha384-lBwkNqUPv+IBbznagiFakY2BOIueq/Bg89wHO2XeM9YAZ51rPkrvdMd+XmFupHsM",
"/build/dashboard.18f5a8b7.css": "sha384-PBD8ftb2yBoSjRe2sN5Xb4dEz045kOEUfcIufdSis+tWoWdAx5j4Yic+F/6CYbrP" "/build/dashboard.18f5a8b7.css": "sha384-PBD8ftb2yBoSjRe2sN5Xb4dEz045kOEUfcIufdSis+tWoWdAx5j4Yic+F/6CYbrP"

View File

@@ -1,5 +1,5 @@
{ {
"build/app.css": "/build/app.a61c91dc.css", "build/app.css": "/build/app.f82609fb.css",
"build/app.js": "/build/app.99c7b6b4.js", "build/app.js": "/build/app.99c7b6b4.js",
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css", "build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
"build/export-pdf.js": "/build/export-pdf.d367a32e.js", "build/export-pdf.js": "/build/export-pdf.d367a32e.js",
@@ -9,7 +9,7 @@
"build/invoice-pdf.js": "/build/invoice-pdf.ce9aace0.js", "build/invoice-pdf.js": "/build/invoice-pdf.ce9aace0.js",
"build/chart.js": "/build/chart.f5becfac.js", "build/chart.js": "/build/chart.f5becfac.js",
"build/calendar.css": "/build/calendar.bb473428.css", "build/calendar.css": "/build/calendar.bb473428.css",
"build/calendar.js": "/build/calendar.25b368b9.js", "build/calendar.js": "/build/calendar.e7b6fa49.js",
"build/dashboard.css": "/build/dashboard.18f5a8b7.css", "build/dashboard.css": "/build/dashboard.18f5a8b7.css",
"build/dashboard.js": "/build/dashboard.6774a712.js", "build/dashboard.js": "/build/dashboard.6774a712.js",
"build/runtime.js": "/build/runtime.f0079159.js" "build/runtime.js": "/build/runtime.f0079159.js"

View File

@@ -17,11 +17,11 @@ class Constants
/** /**
* The current release version * The current release version
*/ */
public const VERSION = '2.0.27'; public const VERSION = '2.0.28';
/** /**
* The current release: major * 10000 + minor * 100 + patch * The current release: major * 10000 + minor * 100 + patch
*/ */
public const VERSION_ID = 20027; public const VERSION_ID = 20028;
/** /**
* The software name * The software name
*/ */

View File

@@ -33,6 +33,7 @@ final class ContractController extends AbstractController
$dateTimeFactory = $this->getDateTimeFactory($currentUser); $dateTimeFactory = $this->getDateTimeFactory($currentUser);
$canChangeUser = $this->isGranted('contract_other_profile'); $canChangeUser = $this->isGranted('contract_other_profile');
$defaultDate = $dateTimeFactory->createStartOfYear(); $defaultDate = $dateTimeFactory->createStartOfYear();
$now = $dateTimeFactory->createDateTime();
$values = new YearByUser(); $values = new YearByUser();
$values->setUser($currentUser); $values->setUser($currentUser);
@@ -62,7 +63,7 @@ final class ContractController extends AbstractController
/** @var \DateTime $yearDate */ /** @var \DateTime $yearDate */
$yearDate = $values->getDate(); $yearDate = $values->getDate();
$year = $workingTimeService->getYear($profile, $yearDate); $year = $workingTimeService->getYear($profile, $yearDate, $now);
$page = new PageSetup('work_times'); $page = new PageSetup('work_times');
$page->setHelp('contract.html'); $page->setHelp('contract.html');
@@ -73,7 +74,6 @@ final class ContractController extends AbstractController
$controllerEvent = new WorkContractDetailControllerEvent($year); $controllerEvent = new WorkContractDetailControllerEvent($year);
$eventDispatcher->dispatch($controllerEvent); $eventDispatcher->dispatch($controllerEvent);
$now = $dateTimeFactory->createDateTime();
$summary = $workingTimeService->getYearSummary($year, $now); $summary = $workingTimeService->getYearSummary($year, $now);
$boxConfiguration = new BoxConfiguration(); $boxConfiguration = new BoxConfiguration();

View File

@@ -12,6 +12,7 @@ namespace App\Controller;
use App\Configuration\LocaleService; use App\Configuration\LocaleService;
use App\Entity\User; use App\Entity\User;
use App\Event\ConfigureMainMenuEvent; use App\Event\ConfigureMainMenuEvent;
use App\Repository\UserRepository;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -28,7 +29,7 @@ final class HomepageController extends AbstractController
public const DEFAULT_ROUTE = 'timesheet'; public const DEFAULT_ROUTE = 'timesheet';
#[Route(path: '', defaults: [], name: 'homepage', methods: ['GET'])] #[Route(path: '', defaults: [], name: 'homepage', methods: ['GET'])]
public function indexAction(Request $request, LocaleService $service, EventDispatcherInterface $eventDispatcher): Response public function homepage(Request $request, LocaleService $service, EventDispatcherInterface $eventDispatcher, UserRepository $userRepository): Response
{ {
$user = $this->getUser(); $user = $this->getUser();
$userLanguage = $user->getLanguage(); $userLanguage = $user->getLanguage();
@@ -72,8 +73,13 @@ final class HomepageController extends AbstractController
try { try {
return $this->redirectToRoute($route, ['_locale' => $language]); return $this->redirectToRoute($route, ['_locale' => $language]);
} catch (\Exception $ex) { } catch (\Exception $ex) {
$this->logException($ex); if ($route === $userRoute) {
// something is wrong with the url parameters ... // fix invalid routes from old plugins / versions
$user->setPreferenceValue('login_initial_view', 'dashboard');
$userRepository->saveUser($user);
} else {
$this->logException($ex);
}
} }
} }

View File

@@ -60,9 +60,12 @@ final class ProfileController extends AbstractController
{ {
$dateFactory = $this->getDateTimeFactory(); $dateFactory = $this->getDateTimeFactory();
$userStats = $repository->getUserStatistics($profile); $userStats = $repository->getUserStatistics($profile);
$firstEntry = $statisticService->findFirstRecordDate($profile); $workStartingDay = $profile->getWorkStartingDay();
if ($workStartingDay === null) {
$workStartingDay = $statisticService->findFirstRecordDate($profile);
}
$begin = $firstEntry ?? $dateFactory->getStartOfMonth(); $begin = $workStartingDay ?? $dateFactory->getStartOfMonth();
$end = $dateFactory->getEndOfMonth(); $end = $dateFactory->getEndOfMonth();
// statistic service does not fill up the complete year by default! // statistic service does not fill up the complete year by default!
@@ -73,7 +76,7 @@ final class ProfileController extends AbstractController
'tab' => 'charts', 'tab' => 'charts',
'user' => $profile, 'user' => $profile,
'stats' => $userStats, 'stats' => $userStats,
'firstTimesheet' => $firstEntry, 'workingSince' => $workStartingDay,
'workMonths' => $statisticService->getMonthlyStats($begin, $end, [$profile])[0] 'workMonths' => $statisticService->getMonthlyStats($begin, $end, [$profile])[0]
]; ];

View File

@@ -1174,6 +1174,27 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
return (int) $this->getPreferenceValue(UserPreference::WORK_HOURS_SUNDAY, 0); return (int) $this->getPreferenceValue(UserPreference::WORK_HOURS_SUNDAY, 0);
} }
public function getWorkStartingDay(): ?\DateTimeInterface
{
$date = $this->getPreferenceValue(UserPreference::WORK_STARTING_DAY);
if ($date === null) {
return null;
}
try {
$date = \DateTimeImmutable::createFromFormat('Y-m-d h:i:s', $date . ' 00:00:00', new \DateTimeZone($this->getTimezone()));
} catch (Exception $e) {
}
return ($date instanceof \DateTimeInterface) ? $date : null;
}
public function setWorkStartingDay(?\DateTimeInterface $date): void
{
$this->setPreferenceValue(UserPreference::WORK_STARTING_DAY, $date?->format('Y-m-d'));
}
public function getPublicHolidayGroup(): null|string public function getPublicHolidayGroup(): null|string
{ {
$group = $this->getPreferenceValue(UserPreference::PUBLIC_HOLIDAY_GROUP); $group = $this->getPreferenceValue(UserPreference::PUBLIC_HOLIDAY_GROUP);

View File

@@ -38,6 +38,7 @@ class UserPreference
public const WORK_HOURS_FRIDAY = 'work_friday'; public const WORK_HOURS_FRIDAY = 'work_friday';
public const WORK_HOURS_SATURDAY = 'work_saturday'; public const WORK_HOURS_SATURDAY = 'work_saturday';
public const WORK_HOURS_SUNDAY = 'work_sunday'; public const WORK_HOURS_SUNDAY = 'work_sunday';
public const WORK_STARTING_DAY = 'work_start_day';
public const PUBLIC_HOLIDAY_GROUP = 'public_holiday_group'; public const PUBLIC_HOLIDAY_GROUP = 'public_holiday_group';
public const HOLIDAYS_PER_YEAR = 'holidays'; public const HOLIDAYS_PER_YEAR = 'holidays';

View File

@@ -21,7 +21,6 @@ final class ConfigureMainMenuEvent extends Event
private MenuItemModel $apps; private MenuItemModel $apps;
private MenuItemModel $admin; private MenuItemModel $admin;
private MenuItemModel $system; private MenuItemModel $system;
private ?MenuItemModel $root = null;
public function __construct() public function __construct()
{ {
@@ -33,15 +32,23 @@ final class ConfigureMainMenuEvent extends Event
public function findById(string $identifier): ?MenuItemModel public function findById(string $identifier): ?MenuItemModel
{ {
if ($this->root === null) { if (($tmp = $this->menu->findChild($identifier)) !== null) {
$this->root = new MenuItemModel('root', 'root'); return $tmp;
$this->root->addChild($this->menu);
$this->root->addChild($this->apps);
$this->root->addChild($this->admin);
$this->root->addChild($this->system);
} }
return $this->root->findChild($identifier); if (($tmp = $this->apps->findChild($identifier)) !== null) {
return $tmp;
}
if (($tmp = $this->admin->findChild($identifier)) !== null) {
return $tmp;
}
if (($tmp = $this->system->findChild($identifier)) !== null) {
return $tmp;
}
return null;
} }
public function getMenu(): MenuItemModel public function getMenu(): MenuItemModel

View File

@@ -12,12 +12,21 @@ namespace App\Event;
use App\WorkingTime\Model\Year; use App\WorkingTime\Model\Year;
use Symfony\Contracts\EventDispatcher\Event; use Symfony\Contracts\EventDispatcher\Event;
/**
* Working time for every day of the given year.
* Will be reflected in the working-time summary row.
*/
final class WorkingTimeYearEvent extends Event final class WorkingTimeYearEvent extends Event
{ {
public function __construct(private Year $year) public function __construct(private Year $year, private \DateTimeInterface $until)
{ {
} }
public function getUntil(): \DateTimeInterface
{
return $this->until;
}
public function getYear(): Year public function getYear(): Year
{ {
return $this->year; return $this->year;

View File

@@ -15,8 +15,8 @@ use App\Model\Day as BaseDay;
final class Day extends BaseDay final class Day extends BaseDay
{ {
private ?WorkingTime $workingTime = null; private ?WorkingTime $workingTime = null;
/** @var array<string, int> */ /** @var array<DayAddon> */
private array $descriptions = []; private array $addons = [];
public function isLocked(): bool public function isLocked(): bool
{ {
@@ -38,18 +38,27 @@ final class Day extends BaseDay
} }
/** /**
* @return array<string, int> * @return array<DayAddon>
*/ */
public function getDescriptions(): array public function getAddons(): array
{ {
return $this->descriptions; return $this->addons;
}
public function hasAddons(): bool
{
return \count($this->addons) > 0;
} }
/** /**
* Descriptions show up in the approval PDF and maybe in other places as well. * Descriptions show up in the approval PDF and maybe in other places as well.
*/ */
public function addDescription(string $description, int $duration): void public function addAddon(DayAddon $addon): void
{ {
$this->descriptions[$description] = $duration; $this->addons[] = $addon;
if (!$this->isLocked() && $this->workingTime !== null) {
$this->workingTime->setActualTime($this->workingTime->getActualTime() + $addon->getDuration());
}
} }
} }

View File

@@ -0,0 +1,43 @@
<?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\WorkingTime\Model;
final class DayAddon
{
private string $title;
private int $duration;
private bool $billable = true;
public function __construct(string $title, int $duration)
{
$this->title = $title;
$this->duration = $duration;
}
public function getTitle(): string
{
return $this->title;
}
public function getDuration(): int
{
return $this->duration;
}
public function isBillable(): bool
{
return $this->billable;
}
public function setBillable(bool $billable): void
{
$this->billable = $billable;
}
}

View File

@@ -18,6 +18,16 @@ use App\Model\Month as BaseMonth;
*/ */
final class Month extends BaseMonth final class Month extends BaseMonth
{ {
public function __construct(\DateTimeInterface $month, private User $user)
{
parent::__construct($month);
}
public function getUser(): User
{
return $this->user;
}
/** /**
* A month is only locked IF every day is approved. * A month is only locked IF every day is approved.
* If there is even one day left open, the entire month is not locked. * If there is even one day left open, the entire month is not locked.

View File

@@ -30,7 +30,7 @@ final class Year extends BaseYear
protected function createMonth(\DateTimeInterface $month): Month protected function createMonth(\DateTimeInterface $month): Month
{ {
return new Month($month); return new Month($month, $this->user);
} }
public function getExpectedTime(\DateTimeInterface $until): int public function getExpectedTime(\DateTimeInterface $until): int

View File

@@ -46,7 +46,7 @@ final class WorkingTimeService
return $this->workingTimeRepository->getLatestApproval($user); return $this->workingTimeRepository->getLatestApproval($user);
} }
public function getYear(User $user, \DateTimeInterface $yearDate): Year public function getYear(User $user, \DateTimeInterface $yearDate, \DateTimeInterface $until): Year
{ {
$yearTimes = $this->workingTimeRepository->findForYear($user, $yearDate); $yearTimes = $this->workingTimeRepository->findForYear($user, $yearDate);
$existing = []; $existing = [];
@@ -57,6 +57,7 @@ final class WorkingTimeService
$year = new Year(\DateTimeImmutable::createFromInterface($yearDate), $user); $year = new Year(\DateTimeImmutable::createFromInterface($yearDate), $user);
$stats = null; $stats = null;
$firstDay = $user->getWorkStartingDay();
foreach ($year->getMonths() as $month) { foreach ($year->getMonths() as $month) {
foreach ($month->getDays() as $day) { foreach ($month->getDays() as $day) {
@@ -70,8 +71,12 @@ final class WorkingTimeService
$stats = $this->getYearStatistics($yearDate, $user); $stats = $this->getYearStatistics($yearDate, $user);
} }
$result = new WorkingTime($user, $day->getDay()); $dayDate = $day->getDay();
$result->setExpectedTime($user->getWorkHoursForDay($day->getDay())); $result = new WorkingTime($user, $dayDate);
if ($firstDay === null || $firstDay <= $dayDate) {
$result->setExpectedTime($user->getWorkHoursForDay($dayDate));
}
if (\array_key_exists($key, $stats)) { if (\array_key_exists($key, $stats)) {
$result->setActualTime($stats[$key]); $result->setActualTime($stats[$key]);
@@ -81,16 +86,16 @@ final class WorkingTimeService
} }
} }
$event = new WorkingTimeYearEvent($year); $event = new WorkingTimeYearEvent($year, $until);
$this->eventDispatcher->dispatch($event); $this->eventDispatcher->dispatch($event);
return $year; return $year;
} }
public function getMonth(User $user, \DateTimeInterface $monthDate): Month public function getMonth(User $user, \DateTimeInterface $monthDate, \DateTimeInterface $until): Month
{ {
// uses the year, because that triggers the required events to collect all different working times // uses the year, because that triggers the required events to collect all different working times
$year = $this->getYear($user, $monthDate); $year = $this->getYear($user, $monthDate, $until);
return $year->getMonth($monthDate); return $year->getMonth($monthDate);
} }

View File

@@ -212,7 +212,7 @@
</td> </td>
{% set dayCount = 0 %} {% set dayCount = 0 %}
{% for day in month.days %} {% for day in month.days %}
{% set class = 'text-end contractDay' %} {% set class = 'text-end contractDay text-nowrap' %}
{% if day.day is weekend %} {% if day.day is weekend %}
{% set class = class ~ ' weekend' %} {% set class = class ~ ' weekend' %}
{% endif %} {% endif %}
@@ -223,6 +223,16 @@
{% endif %} {% endif %}
<td class="{{ class }}"> <td class="{{ class }}">
{% if (day.workingTime.expectedTime != 0 or day.workingTime.actualTime != 0) and (now > day.day or day.workingTime.actualTime > 0) %} {% if (day.workingTime.expectedTime != 0 or day.workingTime.actualTime != 0) and (now > day.day or day.workingTime.actualTime > 0) %}
{% if day.hasAddons() %}
{% set statusTitle = '' %}
{% for addon in day.getAddons() %}
{% set statusTitle = statusTitle ~ (addon.title|trans) ~ ' (' ~ addon.duration|duration(decimal) ~ ')' %}
{% if not loop.last %}
{% set statusTitle = statusTitle ~ '<br>' %}
{% endif %}
{% endfor %}
<span class="status-dot status-azure" data-toggle="tooltip" data-placement="top" title="{{ statusTitle }}"></span>
{% endif %}
{{ work_times_result(day.workingTime.expectedTime, day.workingTime.actualTime, decimal) }} {{ work_times_result(day.workingTime.expectedTime, day.workingTime.actualTime, decimal) }}
{% endif %} {% endif %}
</td> </td>

View File

@@ -22,7 +22,7 @@
}) %} }) %}
{% endif %} {% endif %}
{% set datagrid = datagrid|merge({ {% set datagrid = datagrid|merge({
'profile.first_entry' : (firstTimesheet is not null ? firstTimesheet|date_short : '–'), 'profile.first_entry' : (workingSince is not null ? workingSince|date_short : '–'),
'profile.registration_date' : user.registeredAt|date_short, 'profile.registration_date' : user.registeredAt|date_short,
}) %} }) %}
{% set seeOwnRate = false %} {% set seeOwnRate = false %}

View File

@@ -18,7 +18,7 @@ use PHPUnit\Framework\TestCase;
*/ */
class ConfigureMainMenuEventTest extends TestCase class ConfigureMainMenuEventTest extends TestCase
{ {
public function testGetterAndSetter() public function testGetterAndSetter(): void
{ {
$sut = new ConfigureMainMenuEvent(); $sut = new ConfigureMainMenuEvent();

View File

@@ -24,8 +24,9 @@ class WorkingTimeYearEventTest extends TestCase
$user = new User(); $user = new User();
$date = new \DateTime('2023-02-10'); $date = new \DateTime('2023-02-10');
$year = new Year($date, $user); $year = new Year($date, $user);
$sut = new WorkingTimeYearEvent($year); $sut = new WorkingTimeYearEvent($year, clone $date);
self::assertSame($year, $sut->getYear()); self::assertSame($year, $sut->getYear());
self::assertEquals('2023-02-10', $sut->getUntil()->format('Y-m-d'));
} }
} }

View File

@@ -22,12 +22,15 @@ class MonthTest extends TestCase
{ {
public function testDefaults(): void public function testDefaults(): void
{ {
$user = new User();
$user->setUsername('foo-bar');
$months = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; $months = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
foreach($months as $key => $days) { foreach($months as $key => $days) {
$index = ++$key; $index = ++$key;
$monthKey = ($index < 10) ? '0' . $index : $index; $monthKey = ($index < 10) ? '0' . $index : $index;
$date = new \DateTimeImmutable(sprintf('2020-%s-25 13:00:00', $monthKey)); $date = new \DateTimeImmutable(sprintf('2020-%s-25 13:00:00', $monthKey));
$month = new Month($date); $month = new Month($date, $user);
self::assertEquals(sprintf('2020-%s-25', $monthKey), $month->getMonth()->format('Y-m-d')); self::assertEquals(sprintf('2020-%s-25', $monthKey), $month->getMonth()->format('Y-m-d'));
self::assertCount($days, $month->getDays()); self::assertCount($days, $month->getDays());
self::assertFalse($month->isLocked()); self::assertFalse($month->isLocked());

View File

@@ -4982,11 +4982,6 @@ parameters:
count: 1 count: 1
path: Event/CalendarGoogleSourceEventTest.php path: Event/CalendarGoogleSourceEventTest.php
-
message: "#^Method App\\\\Tests\\\\Event\\\\ConfigureMainMenuEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#"
count: 1
path: Event/ConfigureMainMenuEventTest.php
- -
message: "#^Method App\\\\Tests\\\\Event\\\\CustomerMetaDefinitionEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#" message: "#^Method App\\\\Tests\\\\Event\\\\CustomerMetaDefinitionEventTest\\:\\:testGetterAndSetter\\(\\) has no return type specified\\.$#"
count: 1 count: 1