Release 2.39 (#5604)
* prepare audit via annotation * default calendar slot label distance of 1h + replace freestyle config with dropdown * added missing return definition in callbacks * refactor view name handling * dispatch calendar view changes and push them into the URL to be able to reload the poage * bump packages * fix timezone issue in calendar sum calculation * fixes #5618 resetRates() * show expected daily hours in working-contract screen
This commit is contained in:
@@ -73,21 +73,6 @@ export default class KimaiCalendar {
|
|||||||
/** @type {KimaiAlert} ALERT */
|
/** @type {KimaiAlert} ALERT */
|
||||||
const ALERT = this.kimai.getPlugin('alert');
|
const ALERT = this.kimai.getPlugin('alert');
|
||||||
|
|
||||||
let initialView = 'dayGridMonth';
|
|
||||||
switch (options['initialView']) {
|
|
||||||
case 'month':
|
|
||||||
initialView = 'dayGridMonth';
|
|
||||||
break;
|
|
||||||
case 'agendaWeek':
|
|
||||||
case 'week':
|
|
||||||
initialView = 'timeGridWeek';
|
|
||||||
break;
|
|
||||||
case 'agendaDay':
|
|
||||||
case 'day':
|
|
||||||
initialView = 'timeGridDay';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instead of using "buttonIcons" the theme needs to be adjusted directly
|
// Instead of using "buttonIcons" the theme needs to be adjusted directly
|
||||||
// https://fullcalendar.io/docs/buttonIcons
|
// https://fullcalendar.io/docs/buttonIcons
|
||||||
BootstrapTheme.prototype.classes = {
|
BootstrapTheme.prototype.classes = {
|
||||||
@@ -120,7 +105,8 @@ export default class KimaiCalendar {
|
|||||||
esLocale, euLocale, faLocale, fiLocale, frLocale, heLocale, hrLocale, huLocale, itLocale, jaLocale, koLocale,
|
esLocale, euLocale, faLocale, fiLocale, frLocale, heLocale, hrLocale, huLocale, itLocale, jaLocale, koLocale,
|
||||||
nbLocale, nlLocale, plLocale, ptLocale, ptBrLocale, roLocale, ruLocale, skLocale, svLocale, trLocale, zhLocale, viLocale ],
|
nbLocale, nlLocale, plLocale, ptLocale, ptBrLocale, roLocale, ruLocale, skLocale, svLocale, trLocale, zhLocale, viLocale ],
|
||||||
plugins: [ bootstrap5Plugin, dayGridPlugin, timeGridPlugin, googlePlugin, iCalendarPlugin, interactionPlugin ],
|
plugins: [ bootstrap5Plugin, dayGridPlugin, timeGridPlugin, googlePlugin, iCalendarPlugin, interactionPlugin ],
|
||||||
initialView: initialView,
|
initialView: this.toInternalViewName(this.options['initialView']),
|
||||||
|
initialDate: this.options['initialDate'],
|
||||||
// https://fullcalendar.io/docs/theming
|
// https://fullcalendar.io/docs/theming
|
||||||
themeSystem: 'bootstrap5',
|
themeSystem: 'bootstrap5',
|
||||||
// https://fullcalendar.io/docs/headerToolbar
|
// https://fullcalendar.io/docs/headerToolbar
|
||||||
@@ -155,8 +141,9 @@ export default class KimaiCalendar {
|
|||||||
slotMinTime: this.options['timeframeBegin'] + ':00',
|
slotMinTime: this.options['timeframeBegin'] + ':00',
|
||||||
slotMaxTime: this.options['timeframeEnd'] === '23:59' ? '24:00:00' : (this.options['timeframeEnd'] + ':59'),
|
slotMaxTime: this.options['timeframeEnd'] === '23:59' ? '24:00:00' : (this.options['timeframeEnd'] + ':59'),
|
||||||
|
|
||||||
// auto calculation seems to do the better job, therefor deactivated
|
// deactivate for auto calculation, which does a good job.
|
||||||
//slotLabelInterval: this.options['slotDuration'],
|
// but 1h seems to be a "normal distance" for calendar apps (like Google and Apple)
|
||||||
|
slotLabelInterval: '1:00',
|
||||||
|
|
||||||
// how long should entries look like when they don't have an end
|
// how long should entries look like when they don't have an end
|
||||||
defaultTimedEventDuration: this.options['slotDuration'],
|
defaultTimedEventDuration: this.options['slotDuration'],
|
||||||
@@ -172,14 +159,20 @@ export default class KimaiCalendar {
|
|||||||
// once we can configure working days
|
// once we can configure working days
|
||||||
// hiddenDays: [ 2, 4 ]
|
// hiddenDays: [ 2, 4 ]
|
||||||
|
|
||||||
// when we support holidays and other full day events
|
|
||||||
// allDaySlot: false,
|
|
||||||
// dropAccept
|
// dropAccept
|
||||||
|
|
||||||
dayMaxEventRows: true,
|
dayMaxEventRows: true,
|
||||||
eventMaxStack: this.options['dayLimit'],
|
eventMaxStack: this.options['dayLimit'],
|
||||||
dayMaxEvents: this.options['dayLimit'],
|
dayMaxEvents: this.options['dayLimit'],
|
||||||
|
|
||||||
|
// the callbacks "viewDidMount" and "viewWillUnmount" are only called when switching between month and others, not between week and day
|
||||||
|
datesSet: (dateInfo) => {
|
||||||
|
document.dispatchEvent(new CustomEvent('kimai.calendar.changeDate', {detail: {
|
||||||
|
view: this.toExternalViewName(dateInfo.view.type),
|
||||||
|
date: dateInfo.start.toISOString().split('T')[0],
|
||||||
|
}}));
|
||||||
|
},
|
||||||
|
|
||||||
views: {
|
views: {
|
||||||
dayGrid: {
|
dayGrid: {
|
||||||
dayMaxEventRows: this.options['dayLimit']
|
dayMaxEventRows: this.options['dayLimit']
|
||||||
@@ -535,6 +528,44 @@ export default class KimaiCalendar {
|
|||||||
return (event.source.id.indexOf('kimai-') === 0);
|
return (event.source.id.indexOf('kimai-') === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} viewName
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
toExternalViewName(viewName) {
|
||||||
|
switch(viewName) {
|
||||||
|
case 'timeGridDay':
|
||||||
|
return 'day';
|
||||||
|
case 'timeGridWeek':
|
||||||
|
return 'week';
|
||||||
|
case 'dayGridMonth':
|
||||||
|
default:
|
||||||
|
return 'month';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} viewName
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
toInternalViewName(viewName) {
|
||||||
|
switch(viewName) {
|
||||||
|
case 'day':
|
||||||
|
case 'agendaDay':
|
||||||
|
case 'timeGridDay':
|
||||||
|
return 'timeGridDay';
|
||||||
|
case 'week':
|
||||||
|
case 'agendaWeek':
|
||||||
|
case 'timeGridWeek':
|
||||||
|
return 'timeGridWeek';
|
||||||
|
case 'month':
|
||||||
|
case 'agendaMonth':
|
||||||
|
case 'dayGridMonth':
|
||||||
|
default:
|
||||||
|
return 'dayGridMonth';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
@@ -718,16 +749,16 @@ export default class KimaiCalendar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
events.forEach(item => {
|
events.forEach(item => {
|
||||||
const start = DateTime.fromJSDate(item.start);
|
const start = DateTime.fromJSDate(item.start).toUTC();
|
||||||
|
const dateStr = start.toFormat('yyyy-MM-dd');
|
||||||
|
|
||||||
const dateStr = start.toISODate();
|
|
||||||
if (!durations[dateStr]) {
|
if (!durations[dateStr]) {
|
||||||
durations[dateStr] = 0;
|
durations[dateStr] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// absences or public holidays are all day
|
// absences or public holidays are all day
|
||||||
if (item.end !== null) {
|
if (item.end !== null) {
|
||||||
const end = DateTime.fromJSDate(item.end);
|
const end = DateTime.fromJSDate(item.end).toUTC();
|
||||||
const duration = end.diff(start, 'hours').as('seconds');
|
const duration = end.diff(start, 'hours').as('seconds');
|
||||||
durations[dateStr] += duration;
|
durations[dateStr] += duration;
|
||||||
}
|
}
|
||||||
|
|||||||
613
composer.lock
generated
613
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,11 +6,9 @@ parameters:
|
|||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
_defaults:
|
_defaults:
|
||||||
autowire: true # Automatically injects dependencies in your services.
|
autowire: true
|
||||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
autoconfigure: true
|
||||||
public: false # Allows optimizing the container by removing unused services; this also means
|
public: false
|
||||||
# fetching services directly from the container via $container->get() won't work.
|
|
||||||
# The best practice is to be explicit about your dependencies anyway.
|
|
||||||
bind:
|
bind:
|
||||||
$projectDirectory: '%kernel.project_dir%'
|
$projectDirectory: '%kernel.project_dir%'
|
||||||
$kernelEnvironment: '%kernel.environment%'
|
$kernelEnvironment: '%kernel.environment%'
|
||||||
|
|||||||
1
kimai.sh
1
kimai.sh
@@ -22,6 +22,7 @@ function update_kimai() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
rm -rf var/sessions/ 2>&1
|
||||||
rm -rf var/cache/* 2>&1
|
rm -rf var/cache/* 2>&1
|
||||||
git fetch --tags
|
git fetch --tags
|
||||||
git checkout "$VERSION"
|
git checkout "$VERSION"
|
||||||
|
|||||||
@@ -663,11 +663,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Controller/ActivityController.php
|
path: src/Controller/ActivityController.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot access offset 'user' on mixed\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Controller/CalendarController.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$name of class App\\\\Entity\\\\Team constructor expects string, string\\|null given\\.$#"
|
message: "#^Parameter \\#1 \\$name of class App\\\\Entity\\\\Team constructor expects string, string\\|null given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,23 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap v5.3.3 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* [KIMAI] KimaiCalendar: wrapping Fullcalendar.io
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* [KIMAI] KimaiColor: handle colors
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* [KIMAI] KimaiContextMenu: help to create, position and display context menus
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
FullCalendar v5.11.5
|
|
||||||
Docs & License: https://fullcalendar.io/
|
|
||||||
(c) 2022 Adam Shaw
|
|
||||||
*/
|
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"calendar": {
|
"calendar": {
|
||||||
"js": [
|
"js": [
|
||||||
"/build/runtime.6c399d29.js",
|
"/build/runtime.6c399d29.js",
|
||||||
"/build/calendar.f4379767.js"
|
"/build/calendar.d82e420f.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"/build/calendar.d757753e.css"
|
"/build/calendar.d757753e.css"
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"/build/invoice-pdf.26d98626.js": "sha384-gwNzQiU1y6qU/M9DPGiNW0MVZkLctEHk37sCES2X9ov+zugEaDABdkMjKBYOC9lz",
|
"/build/invoice-pdf.26d98626.js": "sha384-gwNzQiU1y6qU/M9DPGiNW0MVZkLctEHk37sCES2X9ov+zugEaDABdkMjKBYOC9lz",
|
||||||
"/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7",
|
"/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7",
|
||||||
"/build/chart.62631acc.js": "sha384-L4evSO0OZiQt+jTqfMR70M2Vid7Hl5YdPocC6syhSoBRavlf7Az9/Ldh4YJUuwAU",
|
"/build/chart.62631acc.js": "sha384-L4evSO0OZiQt+jTqfMR70M2Vid7Hl5YdPocC6syhSoBRavlf7Az9/Ldh4YJUuwAU",
|
||||||
"/build/calendar.f4379767.js": "sha384-fNnf3iqMaALA89ncvi3nHAlJ2vlI1gjuL1fqnktU+RKVKTrVFCGuDrlIurNHrggx",
|
"/build/calendar.d82e420f.js": "sha384-jJg6+vV6XQn7YlY5wBwvBAujWGgYhSMCS+eDFFxoir+Y69UKB4Lqc69zP+HFzwts",
|
||||||
"/build/calendar.d757753e.css": "sha384-cTmQMgHYjd2gfObFWmEUph7qQLCyXaIkneSf+bQ2mqVmZwqOB+pJOm/UYTyTjALJ",
|
"/build/calendar.d757753e.css": "sha384-cTmQMgHYjd2gfObFWmEUph7qQLCyXaIkneSf+bQ2mqVmZwqOB+pJOm/UYTyTjALJ",
|
||||||
"/build/dashboard.632f98fb.js": "sha384-PlHarP53f8b+47VZvbQw3LURA2vODFf7UMwpnktvukrhswaHtx93cx2M1BtO56JH",
|
"/build/dashboard.632f98fb.js": "sha384-PlHarP53f8b+47VZvbQw3LURA2vODFf7UMwpnktvukrhswaHtx93cx2M1BtO56JH",
|
||||||
"/build/dashboard.b7129fa1.css": "sha384-2nn5hLA+3YedgHYBpge62S8Losj8aoPwK9Zk9EvN1xEYatvOUQ7H3rIR2UUJAGOS",
|
"/build/dashboard.b7129fa1.css": "sha384-2nn5hLA+3YedgHYBpge62S8Losj8aoPwK9Zk9EvN1xEYatvOUQ7H3rIR2UUJAGOS",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"build/invoice-pdf.js": "/build/invoice-pdf.26d98626.js",
|
"build/invoice-pdf.js": "/build/invoice-pdf.26d98626.js",
|
||||||
"build/chart.js": "/build/chart.62631acc.js",
|
"build/chart.js": "/build/chart.62631acc.js",
|
||||||
"build/calendar.css": "/build/calendar.d757753e.css",
|
"build/calendar.css": "/build/calendar.d757753e.css",
|
||||||
"build/calendar.js": "/build/calendar.f4379767.js",
|
"build/calendar.js": "/build/calendar.d82e420f.js",
|
||||||
"build/dashboard.css": "/build/dashboard.b7129fa1.css",
|
"build/dashboard.css": "/build/dashboard.b7129fa1.css",
|
||||||
"build/dashboard.js": "/build/dashboard.632f98fb.js",
|
"build/dashboard.js": "/build/dashboard.632f98fb.js",
|
||||||
"build/highlight.css": "/build/highlight.98bf3927.css",
|
"build/highlight.css": "/build/highlight.98bf3927.css",
|
||||||
|
|||||||
21
src/Audit/Loggable.php
Normal file
21
src/Audit/Loggable.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?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\Audit;
|
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||||
|
final class Loggable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param class-string|null $customFieldClass
|
||||||
|
*/
|
||||||
|
public function __construct(public ?string $customFieldClass = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Audit/Versioned.php
Normal file
18
src/Audit/Versioned.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?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\Audit;
|
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||||
|
final class Versioned
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/Calendar/CalendarQuery.php
Normal file
55
src/Calendar/CalendarQuery.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?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\Calendar;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Form\Type\CalendarViewType;
|
||||||
|
use DateTimeInterface;
|
||||||
|
|
||||||
|
class CalendarQuery
|
||||||
|
{
|
||||||
|
private ?DateTimeInterface $date = null;
|
||||||
|
private string $view = CalendarViewType::DEFAULT_VIEW;
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
public function getDate(): ?DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDate(?DateTimeInterface $date): void
|
||||||
|
{
|
||||||
|
$this->date = $date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getView(): string
|
||||||
|
{
|
||||||
|
return $this->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setView(string $view): void
|
||||||
|
{
|
||||||
|
$this->view = match($view){
|
||||||
|
'agendaDay', 'day' => 'day',
|
||||||
|
'agendaWeek', 'week' => 'week',
|
||||||
|
default => 'month',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(?User $user): void
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,11 +17,11 @@ final class Constants
|
|||||||
/**
|
/**
|
||||||
* The current release version
|
* The current release version
|
||||||
*/
|
*/
|
||||||
public const VERSION = '2.38.0';
|
public const VERSION = '2.39.0';
|
||||||
/**
|
/**
|
||||||
* The current release: major * 10000 + minor * 100 + patch
|
* The current release: major * 10000 + minor * 100 + patch
|
||||||
*/
|
*/
|
||||||
public const VERSION_ID = 23800;
|
public const VERSION_ID = 23900;
|
||||||
/**
|
/**
|
||||||
* The software name
|
* The software name
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,15 +9,18 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Calendar\CalendarQuery;
|
||||||
use App\Calendar\CalendarService;
|
use App\Calendar\CalendarService;
|
||||||
use App\Configuration\SystemConfiguration;
|
use App\Configuration\SystemConfiguration;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Form\CalendarForm;
|
use App\Form\Toolbar\CalendarToolbarForm;
|
||||||
|
use App\Form\Type\CalendarViewType;
|
||||||
use App\Timesheet\TrackingModeService;
|
use App\Timesheet\TrackingModeService;
|
||||||
use App\Utils\PageSetup;
|
use App\Utils\PageSetup;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,34 +38,41 @@ final class CalendarController extends AbstractController
|
|||||||
#[Route(path: '/{profile}', name: 'calendar_user', methods: ['GET'])]
|
#[Route(path: '/{profile}', name: 'calendar_user', methods: ['GET'])]
|
||||||
public function userCalendar(Request $request): Response
|
public function userCalendar(Request $request): Response
|
||||||
{
|
{
|
||||||
$form = null;
|
$currentUser = $this->getUser();
|
||||||
$profile = $this->getUser();
|
$profile = $currentUser;
|
||||||
|
$canChangeUser = $this->isGranted('view_other_timesheet');
|
||||||
|
|
||||||
if ($this->isGranted('view_other_timesheet')) {
|
$query = new CalendarQuery();
|
||||||
$form = $this->createFormForGetRequest(CalendarForm::class, ['user' => $profile], [
|
$query->setUser($profile);
|
||||||
|
$query->setDate($this->getDateTimeFactory($profile)->create());
|
||||||
|
|
||||||
|
$defaultView = CalendarViewType::DEFAULT_VIEW;
|
||||||
|
$userView = $profile->getPreference('calendar_initial_view')?->getValue();
|
||||||
|
if ($userView !== null) {
|
||||||
|
$defaultView = (string) $userView;
|
||||||
|
}
|
||||||
|
$query->setView($defaultView);
|
||||||
|
|
||||||
|
$form = $this->createFormForGetRequest(CalendarToolbarForm::class, $query, [
|
||||||
'action' => $this->generateUrl('calendar'),
|
'action' => $this->generateUrl('calendar'),
|
||||||
|
'change_user' => $canChangeUser,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$form->handleRequest($request);
|
$form->submit($request->query->all(), false);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($query->getUser() === null) {
|
||||||
$values = $form->getData();
|
$query->setUser($currentUser);
|
||||||
if ($values['user'] instanceof User) {
|
|
||||||
$profile = $values['user'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = $form->createView();
|
/** @var User $profile */
|
||||||
|
$profile = $query->getUser();
|
||||||
|
|
||||||
// hide if the current user is the only available one
|
if ($currentUser !== $profile && !$canChangeUser) {
|
||||||
if (\count($form->offsetGet('user')->vars['choices']) < 2) {
|
throw new AccessDeniedException('User is not allowed to see other users calendar');
|
||||||
$form = null;
|
|
||||||
$profile = $this->getUser();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$mode = $this->service->getActiveMode();
|
$mode = $this->service->getActiveMode();
|
||||||
$factory = $this->getDateTimeFactory();
|
$factory = $this->getDateTimeFactory($profile);
|
||||||
|
|
||||||
// if now is default time, we do not pass it on, so it can be re-calculated for each new entry
|
// if now is default time, we do not pass it on, so it can be re-calculated for each new entry
|
||||||
$defaultStart = null;
|
$defaultStart = null;
|
||||||
@@ -89,7 +99,9 @@ final class CalendarController extends AbstractController
|
|||||||
|
|
||||||
return $this->render('calendar/user.html.twig', [
|
return $this->render('calendar/user.html.twig', [
|
||||||
'page_setup' => $page,
|
'page_setup' => $page,
|
||||||
'form' => $form,
|
'initial_view' => $query->getView(),
|
||||||
|
'initial_date' => $query->getDate(),
|
||||||
|
'form' => $form->createView(),
|
||||||
'user' => $profile,
|
'user' => $profile,
|
||||||
'config' => $config,
|
'config' => $config,
|
||||||
'dragAndDrop' => $dragAndDrop,
|
'dragAndDrop' => $dragAndDrop,
|
||||||
|
|||||||
@@ -83,8 +83,25 @@ final class ContractController extends AbstractController
|
|||||||
$boxConfiguration->setDecimal(false);
|
$boxConfiguration->setDecimal(false);
|
||||||
$boxConfiguration->setCollapsed($summary->count() > 0);
|
$boxConfiguration->setCollapsed($summary->count() > 0);
|
||||||
|
|
||||||
|
$hasConfiguration = $profile->hasWorkHourConfiguration();
|
||||||
|
$days = [];
|
||||||
|
if ($hasConfiguration) {
|
||||||
|
$calculator = $workingTimeService->getContractMode($profile)->getCalculator($profile);
|
||||||
|
$start = $dateTimeFactory->getStartOfWeek();
|
||||||
|
$end = $dateTimeFactory->getEndOfWeek();
|
||||||
|
while ($start < $end) {
|
||||||
|
$tmp = clone $start;
|
||||||
|
$days[] = [
|
||||||
|
'date' => $tmp,
|
||||||
|
'duration' => $calculator->isWorkDay($tmp) ? $calculator->getWorkHoursForDay($tmp) : null
|
||||||
|
];
|
||||||
|
$start = $start->add(new \DateInterval('P1D'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('contract/status.html.twig', [
|
return $this->render('contract/status.html.twig', [
|
||||||
'withWorkHourConfiguration' => $profile->hasWorkHourConfiguration(),
|
'days' => $days,
|
||||||
|
'withWorkHourConfiguration' => $hasConfiguration,
|
||||||
'box_configuration' => $boxConfiguration,
|
'box_configuration' => $boxConfiguration,
|
||||||
'page_setup' => $page,
|
'page_setup' => $page,
|
||||||
'decimal' => $boxConfiguration->isDecimal(),
|
'decimal' => $boxConfiguration->isDecimal(),
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ use App\Validator\Constraints\ColorChoices;
|
|||||||
use App\Validator\Constraints\DateTimeFormat;
|
use App\Validator\Constraints\DateTimeFormat;
|
||||||
use App\Validator\Constraints\TimeFormat;
|
use App\Validator\Constraints\TimeFormat;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CountryType;
|
use Symfony\Component\Form\Extension\Core\Type\CountryType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
|
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||||
@@ -587,7 +588,8 @@ final class SystemConfigurationController extends AbstractController
|
|||||||
->setConstraints([new NotBlank(), new TimeFormat()]),
|
->setConstraints([new NotBlank(), new TimeFormat()]),
|
||||||
(new Configuration('calendar.slot_duration'))
|
(new Configuration('calendar.slot_duration'))
|
||||||
->setTranslationDomain('system-configuration')
|
->setTranslationDomain('system-configuration')
|
||||||
->setType(TextType::class)
|
->setType(ChoiceType::class)
|
||||||
|
->setOptions(['choices' => ['00:15' => '00:15:00', '00:30' => '00:30:00', '01:00' => '01:00:00']])
|
||||||
->setConstraints([new Regex(['pattern' => '/[0-2]{1}[0-9]{1}:[0-9]{2}:[0-9]{2}/']), new NotNull()]),
|
->setConstraints([new Regex(['pattern' => '/[0-2]{1}[0-9]{1}:[0-9]{2}:[0-9]{2}/']), new NotNull()]),
|
||||||
(new Configuration('calendar.dragdrop_amount'))
|
(new Configuration('calendar.dragdrop_amount'))
|
||||||
->setTranslationDomain('system-configuration')
|
->setTranslationDomain('system-configuration')
|
||||||
|
|||||||
@@ -365,9 +365,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($dto->isRecalculateRates()) {
|
if ($dto->isRecalculateRates()) {
|
||||||
$timesheet->setFixedRate(null);
|
$timesheet->resetRates();
|
||||||
$timesheet->setHourlyRate(null);
|
|
||||||
$timesheet->setInternalRate(null);
|
|
||||||
$execute = true;
|
$execute = true;
|
||||||
} elseif (null !== $dto->getFixedRate()) {
|
} elseif (null !== $dto->getFixedRate()) {
|
||||||
$timesheet->setFixedRate($dto->getFixedRate());
|
$timesheet->setFixedRate($dto->getFixedRate());
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ final class TimesheetApiEditForm extends TimesheetEditForm
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
if (\array_key_exists('billable', $data)) {
|
if (\array_key_exists('billable', $data)) {
|
||||||
$data['billableMode'] = Timesheet::BILLABLE_AUTOMATIC;
|
$data['billableMode'] = Timesheet::BILLABLE_AUTOMATIC;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ trait ColorTrait
|
|||||||
// this code exists only for backward compatibility
|
// this code exists only for backward compatibility
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SET_DATA,
|
FormEvents::PRE_SET_DATA,
|
||||||
function (FormEvent $event) use ($required) {
|
function (FormEvent $event) use ($required): void {
|
||||||
if (!$event->getForm()->getConfig()->hasOption('choices')) {
|
if (!$event->getForm()->getConfig()->hasOption('choices')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ trait FormTrait
|
|||||||
// replaces the project select after submission, to make sure only projects for the selected customer are displayed
|
// replaces the project select after submission, to make sure only projects for the selected customer are displayed
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) use ($builder, $project, $customer, $isNew, $options) {
|
function (FormEvent $event) use ($builder, $project, $customer, $isNew, $options): void {
|
||||||
/** @var array<string, mixed> $data */
|
/** @var array<string, mixed> $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
$customer = \array_key_exists('customer', $data) && $data['customer'] !== '' ? $data['customer'] : null;
|
$customer = \array_key_exists('customer', $data) && $data['customer'] !== '' ? $data['customer'] : null;
|
||||||
@@ -114,7 +114,7 @@ trait FormTrait
|
|||||||
// replaces the activity select after submission, to make sure only activities for the selected project are displayed
|
// replaces the activity select after submission, to make sure only activities for the selected project are displayed
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) use ($options) {
|
function (FormEvent $event) use ($options): void {
|
||||||
/** @var array<string, mixed> $data */
|
/** @var array<string, mixed> $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ final class TimesheetMultiUpdate extends AbstractType
|
|||||||
// TODO replace me with FormTrait
|
// TODO replace me with FormTrait
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) use ($project, $customer) {
|
function (FormEvent $event) use ($project, $customer): void {
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
$customer = isset($data['customer']) && !empty($data['customer']) ? $data['customer'] : null;
|
$customer = isset($data['customer']) && !empty($data['customer']) ? $data['customer'] : null;
|
||||||
$project = isset($data['project']) && !empty($data['project']) ? $data['project'] : $project;
|
$project = isset($data['project']) && !empty($data['project']) ? $data['project'] : $project;
|
||||||
@@ -126,7 +126,7 @@ final class TimesheetMultiUpdate extends AbstractType
|
|||||||
// TODO replace me with FormTrait
|
// TODO replace me with FormTrait
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) use ($activityOptions) {
|
function (FormEvent $event) use ($activityOptions): void {
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
if (!isset($data['project']) || empty($data['project'])) {
|
if (!isset($data['project']) || empty($data['project'])) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ class TimesheetEditForm extends AbstractType
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::POST_SET_DATA,
|
FormEvents::POST_SET_DATA,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var Timesheet $timesheet */
|
/** @var Timesheet $timesheet */
|
||||||
$timesheet = $event->getData();
|
$timesheet = $event->getData();
|
||||||
$begin = $timesheet->getBegin();
|
$begin = $timesheet->getBegin();
|
||||||
@@ -203,7 +203,7 @@ class TimesheetEditForm extends AbstractType
|
|||||||
// map single fields to original datetime object
|
// map single fields to original datetime object
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::SUBMIT,
|
FormEvents::SUBMIT,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var Timesheet $data */
|
/** @var Timesheet $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ class TimesheetEditForm extends AbstractType
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::POST_SET_DATA,
|
FormEvents::POST_SET_DATA,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var Timesheet|null $data */
|
/** @var Timesheet|null $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
if (null !== $data->getEnd()) {
|
if (null !== $data->getEnd()) {
|
||||||
@@ -255,7 +255,7 @@ class TimesheetEditForm extends AbstractType
|
|||||||
// make sure that date & time fields are mapped back to begin & end fields
|
// make sure that date & time fields are mapped back to begin & end fields
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::SUBMIT,
|
FormEvents::SUBMIT,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var Timesheet $timesheet */
|
/** @var Timesheet $timesheet */
|
||||||
$timesheet = $event->getData();
|
$timesheet = $event->getData();
|
||||||
$oldEnd = $timesheet->getEnd();
|
$oldEnd = $timesheet->getEnd();
|
||||||
@@ -334,7 +334,7 @@ class TimesheetEditForm extends AbstractType
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::POST_SET_DATA,
|
FormEvents::POST_SET_DATA,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var Timesheet|null $timesheet */
|
/** @var Timesheet|null $timesheet */
|
||||||
$timesheet = $event->getData();
|
$timesheet = $event->getData();
|
||||||
if (null === $timesheet || ($timesheet instanceof Timesheet && $timesheet->isRunning())) {
|
if (null === $timesheet || ($timesheet instanceof Timesheet && $timesheet->isRunning())) {
|
||||||
@@ -346,7 +346,7 @@ class TimesheetEditForm extends AbstractType
|
|||||||
// make sure that duration is mapped back to end field
|
// make sure that duration is mapped back to end field
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::SUBMIT,
|
FormEvents::SUBMIT,
|
||||||
function (FormEvent $event) use ($forceApply) {
|
function (FormEvent $event) use ($forceApply): void {
|
||||||
/** @var Timesheet $timesheet */
|
/** @var Timesheet $timesheet */
|
||||||
$timesheet = $event->getData();
|
$timesheet = $event->getData();
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,24 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form\Toolbar;
|
||||||
|
|
||||||
|
use App\Form\Type\CalendarViewType;
|
||||||
|
use App\Form\Type\DayPickerType;
|
||||||
use App\Form\Type\UserType;
|
use App\Form\Type\UserType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
final class CalendarForm extends AbstractType
|
final class CalendarToolbarForm extends AbstractType
|
||||||
{
|
{
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
|
$builder->add('date', DayPickerType::class, [
|
||||||
|
'model_timezone' => $options['timezone'],
|
||||||
|
'view_timezone' => $options['timezone'],
|
||||||
|
]);
|
||||||
|
$builder->add('view', CalendarViewType::class, []);
|
||||||
$builder->add('user', UserType::class, [
|
$builder->add('user', UserType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'attr' => ['onchange' => 'this.form.submit()']
|
'attr' => ['onchange' => 'this.form.submit()']
|
||||||
@@ -28,7 +35,9 @@ final class CalendarForm extends AbstractType
|
|||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'csrf_protection' => false,
|
'csrf_protection' => false,
|
||||||
|
'timezone' => date_default_timezone_get(),
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
|
'change_user' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ trait ToolbarFormTrait
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject) {
|
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject): void {
|
||||||
/** @var array<string, mixed> $data */
|
/** @var array<string, mixed> $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
$event->getForm()->add($name, CustomerType::class, array_merge([
|
$event->getForm()->add($name, CustomerType::class, array_merge([
|
||||||
@@ -187,7 +187,7 @@ trait ToolbarFormTrait
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject, $multiActivity) {
|
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject, $multiActivity): void {
|
||||||
/** @var array<string, mixed> $data */
|
/** @var array<string, mixed> $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
$event->getForm()->add($name, ProjectType::class, array_merge([
|
$event->getForm()->add($name, ProjectType::class, array_merge([
|
||||||
@@ -266,7 +266,7 @@ trait ToolbarFormTrait
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) use ($name, $multiProject, $activityOptions, $options) {
|
function (FormEvent $event) use ($name, $multiProject, $activityOptions, $options): void {
|
||||||
/** @var array<string, mixed> $data */
|
/** @var array<string, mixed> $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
$event->getForm()->add($name, ActivityType::class, array_merge($activityOptions, [
|
$event->getForm()->add($name, ActivityType::class, array_merge($activityOptions, [
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
namespace App\Form\Type;
|
namespace App\Form\Type;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\CallbackTransformer;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,12 +22,28 @@ final class CalendarViewType extends AbstractType
|
|||||||
{
|
{
|
||||||
public const DEFAULT_VIEW = 'month';
|
public const DEFAULT_VIEW = 'month';
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder->addModelTransformer(new CallbackTransformer(
|
||||||
|
function ($transform) {
|
||||||
|
return match ($transform) {
|
||||||
|
'agendaDay', 'day' => 'day',
|
||||||
|
'agendaWeek', 'week' => 'week',
|
||||||
|
default => self::DEFAULT_VIEW,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
function ($reverseTransform) {
|
||||||
|
return $reverseTransform;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
{
|
{
|
||||||
$choices = [
|
$choices = [
|
||||||
'month' => 'month',
|
'month' => 'month',
|
||||||
'agendaWeek' => 'agendaWeek',
|
'agendaWeek' => 'week',
|
||||||
'agendaDay' => 'agendaDay',
|
'agendaDay' => 'day',
|
||||||
];
|
];
|
||||||
|
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
|
|||||||
43
src/Form/Type/DayPickerType.php
Normal file
43
src/Form/Type/DayPickerType.php
Normal 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\Form\Type;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form field type to enter a date in HTML5 format, mainly for GET forms.
|
||||||
|
*/
|
||||||
|
class DayPickerType extends AbstractType
|
||||||
|
{
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'label' => 'date',
|
||||||
|
'widget' => 'single_text',
|
||||||
|
'html5' => true,
|
||||||
|
'format' => DateType::HTML5_FORMAT,
|
||||||
|
'model_timezone' => date_default_timezone_get(),
|
||||||
|
'view_timezone' => date_default_timezone_get(),
|
||||||
|
'datepicker' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): string
|
||||||
|
{
|
||||||
|
return DateType::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBlockPrefix(): string
|
||||||
|
{
|
||||||
|
return 'day';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ final class EntityMetaDefinitionType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SET_DATA,
|
FormEvents::PRE_SET_DATA,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var MetaTableTypeInterface $definition */
|
/** @var MetaTableTypeInterface $definition */
|
||||||
$definition = $event->getData();
|
$definition = $event->getData();
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ final class ExportColumnsType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
if (\is_array($data)) {
|
if (\is_array($data)) {
|
||||||
$this->ordered = $data; // @phpstan-ignore assign.propertyType
|
$this->ordered = $data; // @phpstan-ignore assign.propertyType
|
||||||
@@ -156,7 +156,7 @@ final class ExportColumnsType extends AbstractType
|
|||||||
);
|
);
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::SUBMIT,
|
FormEvents::SUBMIT,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
$event->setData($this->ordered);
|
$event->setData($this->ordered);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ final class MetaFieldsCollectionType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SET_DATA,
|
FormEvents::PRE_SET_DATA,
|
||||||
function (FormEvent $event) use ($options) {
|
function (FormEvent $event) use ($options): void {
|
||||||
/** @var ArrayCollection<MetaTableTypeInterface> $collection */
|
/** @var ArrayCollection<MetaTableTypeInterface> $collection */
|
||||||
$collection = $event->getData();
|
$collection = $event->getData();
|
||||||
foreach ($collection as $collectionItem) {
|
foreach ($collection as $collectionItem) {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ final class QuickEntryTimesheetType extends AbstractType
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::POST_SET_DATA,
|
FormEvents::POST_SET_DATA,
|
||||||
function (FormEvent $event) use ($durationOptions) {
|
function (FormEvent $event) use ($durationOptions): void {
|
||||||
/** @var Timesheet|null $data */
|
/** @var Timesheet|null $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
if (null === $data || $data->isRunning()) {
|
if (null === $data || $data->isRunning()) {
|
||||||
@@ -89,7 +89,7 @@ final class QuickEntryTimesheetType extends AbstractType
|
|||||||
// make sure that duration is mapped back to end field
|
// make sure that duration is mapped back to end field
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::SUBMIT,
|
FormEvents::SUBMIT,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var Timesheet $data */
|
/** @var Timesheet $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
$duration = $data->getDuration(false);
|
$duration = $data->getDuration(false);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ final class QuickEntryWeekType extends AbstractType
|
|||||||
|
|
||||||
$builder->add('project', ProjectType::class, $projectOptions);
|
$builder->add('project', ProjectType::class, $projectOptions);
|
||||||
|
|
||||||
$projectFunction = function (FormEvent $event) use ($projectOptions) {
|
$projectFunction = function (FormEvent $event) use ($projectOptions): void {
|
||||||
/** @var QuickEntryModel|null $data */
|
/** @var QuickEntryModel|null $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
if ($data === null || $data->getProject() === null) {
|
if ($data === null || $data->getProject() === null) {
|
||||||
@@ -62,7 +62,7 @@ final class QuickEntryWeekType extends AbstractType
|
|||||||
|
|
||||||
$builder->add('activity', ActivityType::class, $activityOptions);
|
$builder->add('activity', ActivityType::class, $activityOptions);
|
||||||
|
|
||||||
$activityFunction = function (FormEvent $event) use ($activityOptions) {
|
$activityFunction = function (FormEvent $event) use ($activityOptions): void {
|
||||||
/** @var QuickEntryModel|null $data */
|
/** @var QuickEntryModel|null $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
if ($data === null || $data->getActivity() === null) {
|
if ($data === null || $data->getActivity() === null) {
|
||||||
@@ -77,7 +77,7 @@ final class QuickEntryWeekType extends AbstractType
|
|||||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, $activityFunction);
|
$builder->addEventListener(FormEvents::PRE_SET_DATA, $activityFunction);
|
||||||
|
|
||||||
// make sure to pre-fill the form, so non-global activities can be loaded for the select project
|
// make sure to pre-fill the form, so non-global activities can be loaded for the select project
|
||||||
$activityPreSubmitFunction = function (FormEvent $event) use ($activityOptions) {
|
$activityPreSubmitFunction = function (FormEvent $event) use ($activityOptions): void {
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
|
|
||||||
if (\is_array($data)) {
|
if (\is_array($data)) {
|
||||||
@@ -113,7 +113,7 @@ final class QuickEntryWeekType extends AbstractType
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) {
|
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options): void {
|
||||||
if ($event->getData() === null && $options['prototype_data'] instanceof QuickEntryModel) {
|
if ($event->getData() === null && $options['prototype_data'] instanceof QuickEntryModel) {
|
||||||
$event->setData(clone $options['prototype_data']);
|
$event->setData(clone $options['prototype_data']);
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ final class QuickEntryWeekType extends AbstractType
|
|||||||
// make sure that duration is mapped back to end field
|
// make sure that duration is mapped back to end field
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::SUBMIT,
|
FormEvents::SUBMIT,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var QuickEntryModel $data */
|
/** @var QuickEntryModel $data */
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
$newRecords = $data->getNewTimesheet();
|
$newRecords = $data->getNewTimesheet();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ final class SystemConfigurationType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SET_DATA,
|
FormEvents::PRE_SET_DATA,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var Configuration $preference */
|
/** @var Configuration $preference */
|
||||||
$preference = $event->getData();
|
$preference = $event->getData();
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ final class TagsSelectType extends AbstractType
|
|||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
|
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options): void {
|
||||||
/** @var array<string> $tagIds */
|
/** @var array<string> $tagIds */
|
||||||
$tagIds = $event->getData();
|
$tagIds = $event->getData();
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ final class UserPreferenceType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SET_DATA,
|
FormEvents::PRE_SET_DATA,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var UserPreference $preference */
|
/** @var UserPreference $preference */
|
||||||
$preference = $event->getData();
|
$preference = $event->getData();
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ final class UserPreferencesCollectionType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SET_DATA,
|
FormEvents::PRE_SET_DATA,
|
||||||
function (FormEvent $event) {
|
function (FormEvent $event): void {
|
||||||
/** @var ArrayCollection<UserPreference> $collection */
|
/** @var ArrayCollection<UserPreference> $collection */
|
||||||
$collection = $event->getData();
|
$collection = $event->getData();
|
||||||
foreach ($collection as $collectionItem) {
|
foreach ($collection as $collectionItem) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ final class ProjectDetailsForm extends AbstractType
|
|||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
function (FormEvent $event) use ($projectOptions) {
|
function (FormEvent $event) use ($projectOptions): void {
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
if (isset($data['project']) && !empty($data['project'])) {
|
if (isset($data['project']) && !empty($data['project'])) {
|
||||||
$projectId = $data['project'];
|
$projectId = $data['project'];
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ final class WorkingTimeCalculatorNone implements WorkingTimeCalculator
|
|||||||
|
|
||||||
public function isWorkDay(\DateTimeInterface $dateTime): bool
|
public function isWorkDay(\DateTimeInterface $dateTime): bool
|
||||||
{
|
{
|
||||||
// we don't know it, so we must assume every day is a a working day
|
// we don't know it, so we must assume every day is a working day
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,19 @@
|
|||||||
{% if form is not null %}
|
{% if form is not null %}
|
||||||
{% embed '@theme/embeds/card.html.twig' %}
|
{% embed '@theme/embeds/card.html.twig' %}
|
||||||
{% block box_body %}
|
{% block box_body %}
|
||||||
{{ form_start(form) }}
|
{{ form_start(form, {attr: {id: 'calendar-form'}}) }}
|
||||||
{% if form.user is defined %}
|
{% if form.user is defined %}
|
||||||
{{ form_row(form.user) }}
|
{% set user_class = '' %}
|
||||||
|
{% if form.user.vars.choices|length < 2 %}
|
||||||
|
{% set user_class = 'd-none' %}
|
||||||
|
{% endif %}
|
||||||
|
{{ form_row(form.user, {row_attr: {class: user_class}}) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.date is defined %}
|
||||||
|
{{ form_row(form.date, {row_attr: {class: 'd-none'}}) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.view is defined %}
|
||||||
|
{{ form_row(form.view, {row_attr: {class: 'd-none'}}) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ form_rest(form) }}
|
{{ form_rest(form) }}
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
@@ -92,15 +102,33 @@
|
|||||||
document.addEventListener('kimai.timesheetUpdate', reloader);
|
document.addEventListener('kimai.timesheetUpdate', reloader);
|
||||||
document.addEventListener('kimai.timesheetDelete', reloader);
|
document.addEventListener('kimai.timesheetDelete', reloader);
|
||||||
|
|
||||||
|
document.addEventListener('kimai.calendar.changeDate', function(event) {
|
||||||
|
var queryParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
const dateSelect = document.getElementById('date');
|
||||||
|
if (dateSelect !== null) {
|
||||||
|
dateSelect.value = event.detail.date;
|
||||||
|
queryParams.set('date', event.detail.date);
|
||||||
|
}
|
||||||
|
const viewSelect = document.getElementById('view');
|
||||||
|
if (viewSelect !== null) {
|
||||||
|
viewSelect.value = event.detail.view;
|
||||||
|
queryParams.set('view', event.detail.view);
|
||||||
|
}
|
||||||
|
|
||||||
|
history.replaceState(null, null, "?" + queryParams.toString());
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('kimai.initialized', function(event) {
|
document.addEventListener('kimai.initialized', function(event) {
|
||||||
const kimai = event.detail.kimai;
|
const kimai = event.detail.kimai;
|
||||||
|
|
||||||
let calendarOptions = {
|
let calendarOptions = {
|
||||||
|
initialDate: '{{ initial_date|report_date }}',
|
||||||
dragdrop: {
|
dragdrop: {
|
||||||
container: '.external-events',
|
container: '.external-events',
|
||||||
items: '.external-event',
|
items: '.external-event',
|
||||||
},
|
},
|
||||||
initialView: '{{ app.user.getPreferenceValue('calendar_initial_view') }}',
|
initialView: '{{ initial_view }}',
|
||||||
translations: {
|
translations: {
|
||||||
customer: '{{ 'customer'|trans }}',
|
customer: '{{ 'customer'|trans }}',
|
||||||
project: '{{ 'project'|trans }}',
|
project: '{{ 'project'|trans }}',
|
||||||
|
|||||||
@@ -222,6 +222,38 @@
|
|||||||
{% endembed %}
|
{% endembed %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if withWorkHourConfiguration %}
|
||||||
|
{% set expectedWeekTimes = 0 %}
|
||||||
|
{% for day in days %}
|
||||||
|
{% set expectedWeekTimes = expectedWeekTimes + day.duration %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% embed '@theme/embeds/collapsible.html.twig' with {id: 'work_contract_should_preview', border: false, item: {options: {bodyExtraClass: 'border-top'}}} %}
|
||||||
|
{% from "macros/status.html.twig" import status_duration %}
|
||||||
|
{% block title %}
|
||||||
|
{{ 'work_times_should'|trans }}
|
||||||
|
|
||||||
|
{{ status_duration(expectedWeekTimes|duration) }}
|
||||||
|
{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<div class="datagrid">
|
||||||
|
{% for day in days %}
|
||||||
|
<div class="datagrid-item">
|
||||||
|
<div class="datagrid-title">{{ day.date|day_name }}</div>
|
||||||
|
<div class="datagrid-content">
|
||||||
|
{% if day.duration is not null %}
|
||||||
|
{{ day.duration|duration }}
|
||||||
|
{% else %}
|
||||||
|
–
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% endembed %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% for controller in boxes %}
|
{% for controller in boxes %}
|
||||||
{{ render(controller(controller, {'year': year, 'boxConfiguration': box_configuration})) }}
|
{{ render(controller(controller, {'year': year, 'boxConfiguration': box_configuration})) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -150,6 +150,25 @@
|
|||||||
</div>
|
</div>
|
||||||
{%- endblock date_widget %}
|
{%- endblock date_widget %}
|
||||||
|
|
||||||
|
{% block day_widget -%}
|
||||||
|
{% set format = 'y-MM-D' %}
|
||||||
|
{% set jsFormat = format|js_format %}
|
||||||
|
{% set attr = attr|merge({'pattern': format|pattern, 'autocomplete': 'off', 'data-format': jsFormat}) -%}
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<a href="#" data-form-widget="date-now" data-format="{{ jsFormat }}" data-target="{{ id }}">{{ icon('calendar') }}</a>
|
||||||
|
</div>
|
||||||
|
{{- block('form_widget_simple') -}}
|
||||||
|
{% if not required %}
|
||||||
|
<span class="input-group-text">
|
||||||
|
<a href="javascript: void(0)" class="link-secondary fs-5" onclick="document.getElementById('{{ id }}').value = ''">
|
||||||
|
{{ icon('cancel') }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{%- endblock day_widget %}
|
||||||
|
|
||||||
{% block time_widget -%}
|
{% block time_widget -%}
|
||||||
{%- set attr = attr|merge({'pattern': time_format|pattern, 'autocomplete': 'off', 'data-timepicker': 'on', 'data-format': js_format, 'placeholder': time_format}) -%}
|
{%- set attr = attr|merge({'pattern': time_format|pattern, 'autocomplete': 'off', 'data-timepicker': 'on', 'data-format': js_format, 'placeholder': time_format}) -%}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
|||||||
35
tests/Audit/LoggableTest.php
Normal file
35
tests/Audit/LoggableTest.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?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\Audit;
|
||||||
|
|
||||||
|
use App\Audit\Loggable;
|
||||||
|
use App\Entity\CustomerMeta;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use Symfony\Component\Form\Test\TypeTestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Loggable::class)]
|
||||||
|
class LoggableTest extends TypeTestCase
|
||||||
|
{
|
||||||
|
public function testConstruct(): void
|
||||||
|
{
|
||||||
|
$sut = new Loggable(CustomerMeta::class);
|
||||||
|
self::assertEquals(CustomerMeta::class, $sut->customFieldClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasAttributeAttributeOnLoggable(): void
|
||||||
|
{
|
||||||
|
$reflection = new \ReflectionClass(Loggable::class);
|
||||||
|
/** @var array<\ReflectionAttribute<\Attribute>> $attributes */
|
||||||
|
$attributes = array_filter($reflection->getAttributes(), fn ($attr) => $attr->getName() === \Attribute::class);
|
||||||
|
self::assertCount(1, $attributes, 'Loggable class should have the Attribute attribute');
|
||||||
|
$attribute = $attributes[0];
|
||||||
|
self::assertEquals(\Attribute::TARGET_CLASS, $attribute->getArguments()[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
tests/Audit/VersionedTest.php
Normal file
34
tests/Audit/VersionedTest.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?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\Audit;
|
||||||
|
|
||||||
|
use App\Audit\Versioned;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use Symfony\Component\Form\Test\TypeTestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Versioned::class)]
|
||||||
|
class VersionedTest extends TypeTestCase
|
||||||
|
{
|
||||||
|
public function testConstruct(): void
|
||||||
|
{
|
||||||
|
$sut = new Versioned();
|
||||||
|
self::assertInstanceOf(Versioned::class, $sut);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasAttributeAttributeOnLoggable(): void
|
||||||
|
{
|
||||||
|
$reflection = new \ReflectionClass(Versioned::class);
|
||||||
|
/** @var array<\ReflectionAttribute<\Attribute>> $attributes */
|
||||||
|
$attributes = array_filter($reflection->getAttributes(), fn ($attr) => $attr->getName() === \Attribute::class);
|
||||||
|
self::assertCount(1, $attributes, 'Versioned class should have the Attribute attribute');
|
||||||
|
$attribute = $attributes[0];
|
||||||
|
self::assertEquals(\Attribute::TARGET_PROPERTY, $attribute->getArguments()[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
tests/Calendar/CalendarQueryTest.php
Normal file
63
tests/Calendar/CalendarQueryTest.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?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\Calendar;
|
||||||
|
|
||||||
|
use App\Calendar\CalendarQuery;
|
||||||
|
use App\Entity\User;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(CalendarQuery::class)]
|
||||||
|
class CalendarQueryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testConstruct(): void
|
||||||
|
{
|
||||||
|
$sut = new CalendarQuery();
|
||||||
|
|
||||||
|
self::assertNull($sut->getDate());
|
||||||
|
self::assertNull($sut->getUser());
|
||||||
|
self::assertEquals('month', $sut->getView());
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$sut->setUser($user);
|
||||||
|
self::assertSame($user, $sut->getUser());
|
||||||
|
|
||||||
|
$date = new \DateTimeImmutable('2025-08-13 12:13:14');
|
||||||
|
$sut->setDate($date);
|
||||||
|
self::assertNotNull($sut->getDate());
|
||||||
|
self::assertEquals('2025-08-13 12:13:14', $sut->getDate()->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
|
$sut->setView('foo');
|
||||||
|
self::assertEquals('month', $sut->getView());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('getTestData')]
|
||||||
|
public function testSetView(string $value, string $expected): void
|
||||||
|
{
|
||||||
|
$sut = new CalendarQuery();
|
||||||
|
$sut->setView($value);
|
||||||
|
self::assertEquals($expected, $sut->getView());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<int, array<int, string>>
|
||||||
|
*/
|
||||||
|
public static function getTestData(): iterable
|
||||||
|
{
|
||||||
|
yield ['agendaMonth', 'month'];
|
||||||
|
yield ['agendaWeek', 'week'];
|
||||||
|
yield ['agendaDay', 'day'];
|
||||||
|
yield ['month', 'month'];
|
||||||
|
yield ['week', 'week'];
|
||||||
|
yield ['day', 'day'];
|
||||||
|
yield ['foo', 'month'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -193,7 +193,7 @@ class InvoiceCreateCommandTest extends KernelTestCase
|
|||||||
|
|
||||||
$fixture = new CustomerFixtures();
|
$fixture = new CustomerFixtures();
|
||||||
$fixture->setAmount(1);
|
$fixture->setAmount(1);
|
||||||
$fixture->setCallback(function (Customer $customer) use ($invoiceTemplate) {
|
$fixture->setCallback(function (Customer $customer) use ($invoiceTemplate): void {
|
||||||
$customer->setInvoiceTemplate($invoiceTemplate[0]);
|
$customer->setInvoiceTemplate($invoiceTemplate[0]);
|
||||||
});
|
});
|
||||||
$customer = $this->importFixture($fixture)[0];
|
$customer = $this->importFixture($fixture)[0];
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class ActivityControllerTest extends AbstractControllerBaseTestCase
|
|||||||
|
|
||||||
$fixture = new ActivityFixtures();
|
$fixture = new ActivityFixtures();
|
||||||
$fixture->setAmount(5);
|
$fixture->setAmount(5);
|
||||||
$fixture->setCallback(function (Activity $activity) {
|
$fixture->setCallback(function (Activity $activity): void {
|
||||||
$activity->setVisible(true);
|
$activity->setVisible(true);
|
||||||
$activity->setComment('I am a foobar with tralalalala some more content');
|
$activity->setComment('I am a foobar with tralalalala some more content');
|
||||||
$activity->setMetaField((new ActivityMeta())->setName('location')->setValue('homeoffice'));
|
$activity->setMetaField((new ActivityMeta())->setName('location')->setValue('homeoffice'));
|
||||||
@@ -111,7 +111,7 @@ class ActivityControllerTest extends AbstractControllerBaseTestCase
|
|||||||
|
|
||||||
$fixture = new ActivityFixtures();
|
$fixture = new ActivityFixtures();
|
||||||
$fixture->setAmount(5);
|
$fixture->setAmount(5);
|
||||||
$fixture->setCallback(function (Activity $activity) {
|
$fixture->setCallback(function (Activity $activity): void {
|
||||||
$activity->setVisible(true);
|
$activity->setVisible(true);
|
||||||
$activity->setComment('I am a foobar with tralalalala some more content');
|
$activity->setComment('I am a foobar with tralalalala some more content');
|
||||||
$activity->setMetaField((new ActivityMeta())->setName('location')->setValue('homeoffice'));
|
$activity->setMetaField((new ActivityMeta())->setName('location')->setValue('homeoffice'));
|
||||||
|
|||||||
@@ -9,7 +9,11 @@
|
|||||||
|
|
||||||
namespace App\Tests\Controller;
|
namespace App\Tests\Controller;
|
||||||
|
|
||||||
|
use App\DataFixtures\UserFixtures;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use App\WorkingTime\Calculator\WorkingTimeCalculatorDay;
|
||||||
|
use App\WorkingTime\Mode\WorkingTimeModeDay;
|
||||||
use PHPUnit\Framework\Attributes\Group;
|
use PHPUnit\Framework\Attributes\Group;
|
||||||
|
|
||||||
#[Group('integration')]
|
#[Group('integration')]
|
||||||
@@ -31,6 +35,34 @@ class ContractControllerTest extends AbstractControllerBaseTestCase
|
|||||||
self::assertEquals(0, $node->count());
|
self::assertEquals(0, $node->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIndexActionWithWorkContract(): void
|
||||||
|
{
|
||||||
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
|
|
||||||
|
/** @var UserRepository $repository */
|
||||||
|
$repository = $this->getPrivateService(UserRepository::class);
|
||||||
|
$user = $this->loadUserFromDatabase(UserFixtures::USERNAME_USER);
|
||||||
|
$user->setWorkContractMode(WorkingTimeModeDay::ID);
|
||||||
|
$user->setPreferenceValue(WorkingTimeCalculatorDay::WORK_HOURS_MONDAY, '28800');
|
||||||
|
$user->setPreferenceValue(WorkingTimeCalculatorDay::WORK_HOURS_TUESDAY, '28800');
|
||||||
|
$user->setPreferenceValue(WorkingTimeCalculatorDay::WORK_HOURS_WEDNESDAY, '28800');
|
||||||
|
$user->setPreferenceValue(WorkingTimeCalculatorDay::WORK_HOURS_THURSDAY, '25200');
|
||||||
|
$user->setPreferenceValue(WorkingTimeCalculatorDay::WORK_HOURS_FRIDAY, '19800');
|
||||||
|
$user->setPreferenceValue(WorkingTimeCalculatorDay::WORK_HOURS_SATURDAY, '0');
|
||||||
|
$user->setPreferenceValue(WorkingTimeCalculatorDay::WORK_HOURS_SUNDAY, '0');
|
||||||
|
$repository->saveUser($user);
|
||||||
|
|
||||||
|
$this->assertAccessIsGranted($client, '/contract');
|
||||||
|
$content = $client->getResponse()->getContent();
|
||||||
|
self::assertNotFalse($content);
|
||||||
|
|
||||||
|
$node = $client->getCrawler()->filter('table#working_times_details');
|
||||||
|
self::assertEquals(1, $node->count());
|
||||||
|
self::assertStringContainsString('7:00', $content);
|
||||||
|
self::assertStringContainsString('8:00', $content);
|
||||||
|
self::assertStringContainsString('5:30', $content);
|
||||||
|
}
|
||||||
|
|
||||||
public function testTeamleadCanChangeUser(): void
|
public function testTeamleadCanChangeUser(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class CustomerControllerTest extends AbstractControllerBaseTestCase
|
|||||||
|
|
||||||
$fixture = new CustomerFixtures();
|
$fixture = new CustomerFixtures();
|
||||||
$fixture->setAmount(5);
|
$fixture->setAmount(5);
|
||||||
$fixture->setCallback(function (Customer $customer) {
|
$fixture->setCallback(function (Customer $customer): void {
|
||||||
$customer->setVisible(true);
|
$customer->setVisible(true);
|
||||||
$customer->setComment('I am a foobar with tralalalala some more content');
|
$customer->setComment('I am a foobar with tralalalala some more content');
|
||||||
$customer->setMetaField((new CustomerMeta())->setName('location')->setValue('homeoffice'));
|
$customer->setMetaField((new CustomerMeta())->setName('location')->setValue('homeoffice'));
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class ExportControllerTest extends AbstractControllerBaseTestCase
|
|||||||
->setUser($user)
|
->setUser($user)
|
||||||
->setAmount(20)
|
->setAmount(20)
|
||||||
->setStartDate($begin)
|
->setStartDate($begin)
|
||||||
->setCallback(function (Timesheet $timesheet) use ($team, $em) {
|
->setCallback(function (Timesheet $timesheet) use ($team, $em): void {
|
||||||
$team->addProject($timesheet->getProject());
|
$team->addProject($timesheet->getProject());
|
||||||
$em->persist($team);
|
$em->persist($team);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class ProjectControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$fixture = new ProjectFixtures();
|
$fixture = new ProjectFixtures();
|
||||||
$fixture->setAmount(5);
|
$fixture->setAmount(5);
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$fixture->setCallback(function (Project $project) use (&$i) {
|
$fixture->setCallback(function (Project $project) use (&$i): void {
|
||||||
$project->setVisible(true);
|
$project->setVisible(true);
|
||||||
switch ($i++) {
|
switch ($i++) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -137,7 +137,7 @@ class ProjectControllerTest extends AbstractControllerBaseTestCase
|
|||||||
|
|
||||||
$fixture = new ProjectFixtures();
|
$fixture = new ProjectFixtures();
|
||||||
$fixture->setAmount(5);
|
$fixture->setAmount(5);
|
||||||
$fixture->setCallback(function (Project $project) {
|
$fixture->setCallback(function (Project $project): void {
|
||||||
$project->setVisible(true);
|
$project->setVisible(true);
|
||||||
$project->setComment('I am a foobar with tralalalala some more content');
|
$project->setComment('I am a foobar with tralalalala some more content');
|
||||||
$project->setMetaField((new ProjectMeta())->setName('location')->setValue('homeoffice'));
|
$project->setMetaField((new ProjectMeta())->setName('location')->setValue('homeoffice'));
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class CustomerMonthlyProjectsControllerTest extends AbstractControllerBaseTestCa
|
|||||||
$projects->setCustomers($customers);
|
$projects->setCustomers($customers);
|
||||||
$projects->setAmount(2);
|
$projects->setAmount(2);
|
||||||
$projects->setIsVisible(true);
|
$projects->setIsVisible(true);
|
||||||
$projects->setCallback(function (Project $project) {
|
$projects->setCallback(function (Project $project): void {
|
||||||
$project->setIsMonthlyBudget();
|
$project->setIsMonthlyBudget();
|
||||||
});
|
});
|
||||||
$this->importFixture($projects);
|
$this->importFixture($projects);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class ProjectDateRangeControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$projects->setCustomers($customers);
|
$projects->setCustomers($customers);
|
||||||
$projects->setAmount(2);
|
$projects->setAmount(2);
|
||||||
$projects->setIsVisible(true);
|
$projects->setIsVisible(true);
|
||||||
$projects->setCallback(function (Project $project) {
|
$projects->setCallback(function (Project $project): void {
|
||||||
$project->setIsMonthlyBudget();
|
$project->setIsMonthlyBudget();
|
||||||
});
|
});
|
||||||
$this->importFixture($projects);
|
$this->importFixture($projects);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class TeamControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||||
$fixture = new TeamFixtures();
|
$fixture = new TeamFixtures();
|
||||||
$fixture->setAmount(5);
|
$fixture->setAmount(5);
|
||||||
$fixture->setCallback(function (Team $team) {
|
$fixture->setCallback(function (Team $team): void {
|
||||||
$team->setName($team->getName() . '- fantastic team with foooo bar magic');
|
$team->setName($team->getName() . '- fantastic team with foooo bar magic');
|
||||||
});
|
});
|
||||||
$this->importFixture($fixture);
|
$this->importFixture($fixture);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class TimesheetControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$fixture->setAmount(5);
|
$fixture->setAmount(5);
|
||||||
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
||||||
$fixture->setStartDate($start);
|
$fixture->setStartDate($start);
|
||||||
$fixture->setCallback(function (Timesheet $timesheet) use ($tags) {
|
$fixture->setCallback(function (Timesheet $timesheet) use ($tags): void {
|
||||||
$timesheet->setDescription('I am a foobar with tralalalala some more content');
|
$timesheet->setDescription('I am a foobar with tralalalala some more content');
|
||||||
$timesheet->setMetaField((new TimesheetMeta())->setName('location')->setValue('homeoffice'));
|
$timesheet->setMetaField((new TimesheetMeta())->setName('location')->setValue('homeoffice'));
|
||||||
$timesheet->setMetaField((new TimesheetMeta())->setName('feature')->setValue('timetracking'));
|
$timesheet->setMetaField((new TimesheetMeta())->setName('feature')->setValue('timetracking'));
|
||||||
@@ -142,7 +142,7 @@ class TimesheetControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$fixture = new TimesheetFixtures();
|
$fixture = new TimesheetFixtures();
|
||||||
$fixture->setAmount(15);
|
$fixture->setAmount(15);
|
||||||
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
||||||
$fixture->setCallback(function (Timesheet $timesheet) {
|
$fixture->setCallback(function (Timesheet $timesheet): void {
|
||||||
$duration = rand(3600, 36000);
|
$duration = rand(3600, 36000);
|
||||||
$begin = new \DateTime('-15 days');
|
$begin = new \DateTime('-15 days');
|
||||||
$end = clone $begin;
|
$end = clone $begin;
|
||||||
@@ -442,7 +442,7 @@ class TimesheetControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$end = new \DateTime('2018-08-02T20:30:00');
|
$end = new \DateTime('2018-08-02T20:30:00');
|
||||||
|
|
||||||
$fixture = new TimesheetFixtures();
|
$fixture = new TimesheetFixtures();
|
||||||
$fixture->setCallback(function (Timesheet $timesheet) use ($begin, $end) {
|
$fixture->setCallback(function (Timesheet $timesheet) use ($begin, $end): void {
|
||||||
$timesheet->setBegin($begin);
|
$timesheet->setBegin($begin);
|
||||||
$timesheet->setEnd($end);
|
$timesheet->setEnd($end);
|
||||||
});
|
});
|
||||||
@@ -474,7 +474,7 @@ class TimesheetControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$fixture->setAmount(1);
|
$fixture->setAmount(1);
|
||||||
$fixture->setIsGlobal(true);
|
$fixture->setIsGlobal(true);
|
||||||
$fixture->setIsVisible(true);
|
$fixture->setIsVisible(true);
|
||||||
$fixture->setCallback(function (Activity $activity) {
|
$fixture->setCallback(function (Activity $activity): void {
|
||||||
$activity->setBudget(1000);
|
$activity->setBudget(1000);
|
||||||
$activity->setTimeBudget(3600);
|
$activity->setTimeBudget(3600);
|
||||||
});
|
});
|
||||||
@@ -523,7 +523,7 @@ class TimesheetControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$fixture->setAmount(1);
|
$fixture->setAmount(1);
|
||||||
$fixture->setIsGlobal(true);
|
$fixture->setIsGlobal(true);
|
||||||
$fixture->setIsVisible(true);
|
$fixture->setIsVisible(true);
|
||||||
$fixture->setCallback(function (Activity $activity) {
|
$fixture->setCallback(function (Activity $activity): void {
|
||||||
$activity->setBudget(1000);
|
$activity->setBudget(1000);
|
||||||
$activity->setTimeBudget(3600);
|
$activity->setTimeBudget(3600);
|
||||||
});
|
});
|
||||||
@@ -813,7 +813,7 @@ class TimesheetControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$fixture->setAmountRunning(0);
|
$fixture->setAmountRunning(0);
|
||||||
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
||||||
$fixture->setStartDate($dateTime->createDateTime());
|
$fixture->setStartDate($dateTime->createDateTime());
|
||||||
$fixture->setCallback(function (Timesheet $timesheet) {
|
$fixture->setCallback(function (Timesheet $timesheet): void {
|
||||||
$timesheet->setDescription('Testing is fun!');
|
$timesheet->setDescription('Testing is fun!');
|
||||||
$begin = clone $timesheet->getBegin();
|
$begin = clone $timesheet->getBegin();
|
||||||
$begin->setTime(0, 0, 0);
|
$begin->setTime(0, 0, 0);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class TimesheetTeamControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$fixture->setAmount(5);
|
$fixture->setAmount(5);
|
||||||
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
||||||
$fixture->setStartDate($start);
|
$fixture->setStartDate($start);
|
||||||
$fixture->setCallback(function (Timesheet $timesheet) {
|
$fixture->setCallback(function (Timesheet $timesheet): void {
|
||||||
$timesheet->setDescription('I am a foobar with tralalalala some more content');
|
$timesheet->setDescription('I am a foobar with tralalalala some more content');
|
||||||
$timesheet->setMetaField((new TimesheetMeta())->setName('location')->setValue('homeoffice'));
|
$timesheet->setMetaField((new TimesheetMeta())->setName('location')->setValue('homeoffice'));
|
||||||
$timesheet->setMetaField((new TimesheetMeta())->setName('feature')->setValue('timetracking'));
|
$timesheet->setMetaField((new TimesheetMeta())->setName('feature')->setValue('timetracking'));
|
||||||
@@ -428,7 +428,7 @@ class TimesheetTeamControllerTest extends AbstractControllerBaseTestCase
|
|||||||
$fixture->setAmountRunning(0);
|
$fixture->setAmountRunning(0);
|
||||||
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
$fixture->setUser($this->getUserByRole(User::ROLE_USER));
|
||||||
$fixture->setStartDate($dateTime->createDateTime());
|
$fixture->setStartDate($dateTime->createDateTime());
|
||||||
$fixture->setCallback(function (Timesheet $timesheet) {
|
$fixture->setCallback(function (Timesheet $timesheet): void {
|
||||||
$timesheet->setDescription('Testing is fun!');
|
$timesheet->setDescription('Testing is fun!');
|
||||||
$begin = clone $timesheet->getBegin();
|
$begin = clone $timesheet->getBegin();
|
||||||
$begin->setTime(0, 0, 0);
|
$begin->setTime(0, 0, 0);
|
||||||
|
|||||||
@@ -22,12 +22,15 @@ class ArrayFormatterTest extends AbstractFormatterTestCase
|
|||||||
return new ArrayFormatter();
|
return new ArrayFormatter();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getActualValue()
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
protected function getActualValue(): array
|
||||||
{
|
{
|
||||||
return ['test', 'foo', 'bar'];
|
return ['test', 'foo', 'bar'];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getExpectedValue()
|
protected function getExpectedValue(): string
|
||||||
{
|
{
|
||||||
return 'test;foo;bar';
|
return 'test;foo;bar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ class BooleanFormatterTest extends AbstractFormatterTestCase
|
|||||||
return new BooleanFormatter();
|
return new BooleanFormatter();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getActualValue()
|
protected function getActualValue(): bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getExpectedValue()
|
protected function getExpectedValue(): bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ class DateFormatterTest extends AbstractFormatterTestCase
|
|||||||
return new DateFormatter();
|
return new DateFormatter();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getActualValue()
|
protected function getActualValue(): \DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->date = new \DateTime();
|
return $this->date = new \DateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getExpectedValue()
|
protected function getExpectedValue(): bool|float
|
||||||
{
|
{
|
||||||
return Date::PHPToExcel($this->date);
|
return Date::PHPToExcel($this->date);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ class DurationFormatterTest extends AbstractFormatterTestCase
|
|||||||
return new DurationFormatter();
|
return new DurationFormatter();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getActualValue()
|
protected function getActualValue(): int
|
||||||
{
|
{
|
||||||
return 3600;
|
return 3600;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getExpectedValue()
|
protected function getExpectedValue(): string
|
||||||
{
|
{
|
||||||
return '=3600/86400';
|
return '=3600/86400';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class TimesheetExportRepositoryTest extends TestCase
|
|||||||
public function testSetExported(): void
|
public function testSetExported(): void
|
||||||
{
|
{
|
||||||
$repository = $this->createMock(TimesheetRepository::class);
|
$repository = $this->createMock(TimesheetRepository::class);
|
||||||
$repository->expects($this->once())->method('setExported')->willReturnCallback(function (array $items) {
|
$repository->expects($this->once())->method('setExported')->willReturnCallback(function (array $items): void {
|
||||||
self::assertCount(2, $items);
|
self::assertCount(2, $items);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
51
tests/Form/Type/CalendarViewTypeTest.php
Normal file
51
tests/Form/Type/CalendarViewTypeTest.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?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\Form\Type;
|
||||||
|
|
||||||
|
use App\Form\Type\CalendarViewType;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
|
use Symfony\Component\Form\Test\TypeTestCase;
|
||||||
|
|
||||||
|
#[CoversClass(CalendarViewType::class)]
|
||||||
|
class CalendarViewTypeTest extends TypeTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return iterable<int, array<int, string>>
|
||||||
|
*/
|
||||||
|
public static function getTestData(): iterable
|
||||||
|
{
|
||||||
|
yield ['month', 'month'];
|
||||||
|
yield ['week', 'week'];
|
||||||
|
yield ['day', 'day'];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('getTestData')]
|
||||||
|
public function testSubmitValidData(string $value, string $expected): void
|
||||||
|
{
|
||||||
|
$data = ['view' => $value];
|
||||||
|
$model = new TypeTestModel(['view' => 'some']);
|
||||||
|
|
||||||
|
$form = $this->factory->createBuilder(FormType::class, $model);
|
||||||
|
$form->add('view', CalendarViewType::class);
|
||||||
|
$form = $form->getForm();
|
||||||
|
|
||||||
|
$expected = new TypeTestModel([
|
||||||
|
'view' => $expected
|
||||||
|
]);
|
||||||
|
|
||||||
|
dump($data);
|
||||||
|
$form->submit($data);
|
||||||
|
|
||||||
|
self::assertTrue($form->isSynchronized());
|
||||||
|
self::assertEquals($expected, $model);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ class TimesheetInvoiceItemRepositoryTest extends TestCase
|
|||||||
public function testSetExported(): void
|
public function testSetExported(): void
|
||||||
{
|
{
|
||||||
$repository = $this->createMock(TimesheetRepository::class);
|
$repository = $this->createMock(TimesheetRepository::class);
|
||||||
$repository->expects($this->once())->method('setExported')->willReturnCallback(function (array $items) {
|
$repository->expects($this->once())->method('setExported')->willReturnCallback(function (array $items): void {
|
||||||
self::assertCount(2, $items);
|
self::assertCount(2, $items);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class SamlLogoutSubscriberTest extends TestCase
|
|||||||
$auth = $this->getMockBuilder(Auth::class)->disableOriginalConstructor()->getMock();
|
$auth = $this->getMockBuilder(Auth::class)->disableOriginalConstructor()->getMock();
|
||||||
$auth->expects($this->once())->method('processSLO')->willThrowException(new Error('blub'));
|
$auth->expects($this->once())->method('processSLO')->willThrowException(new Error('blub'));
|
||||||
$auth->expects($this->once())->method('getSLOurl')->willReturn('/logout');
|
$auth->expects($this->once())->method('getSLOurl')->willReturn('/logout');
|
||||||
$auth->expects($this->once())->method('logout')->willReturnCallback(function () {
|
$auth->expects($this->once())->method('logout')->willReturnCallback(function (): void {
|
||||||
$args = \func_get_args();
|
$args = \func_get_args();
|
||||||
self::assertNull($args[0]);
|
self::assertNull($args[0]);
|
||||||
self::assertEquals([], $args[1]);
|
self::assertEquals([], $args[1]);
|
||||||
|
|||||||
@@ -1301,36 +1301,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: Export/Spreadsheet/CellFormatter/AbstractFormatterTestCase.php
|
path: Export/Spreadsheet/CellFormatter/AbstractFormatterTestCase.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\ArrayFormatterTest\\:\\:getActualValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Export/Spreadsheet/CellFormatter/ArrayFormatterTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\ArrayFormatterTest\\:\\:getExpectedValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Export/Spreadsheet/CellFormatter/ArrayFormatterTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\BooleanFormatterTest\\:\\:getActualValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Export/Spreadsheet/CellFormatter/BooleanFormatterTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\BooleanFormatterTest\\:\\:getExpectedValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Export/Spreadsheet/CellFormatter/BooleanFormatterTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\DateFormatterTest\\:\\:getActualValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Export/Spreadsheet/CellFormatter/DateFormatterTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\DateFormatterTest\\:\\:getExpectedValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Export/Spreadsheet/CellFormatter/DateFormatterTest.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Property App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\DateFormatterTest\\:\\:\\$date has no type specified\\.$#"
|
message: "#^Property App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\DateFormatterTest\\:\\:\\$date has no type specified\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -1351,16 +1321,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: Export/Spreadsheet/CellFormatter/DateTimeFormatterTest.php
|
path: Export/Spreadsheet/CellFormatter/DateTimeFormatterTest.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\DurationFormatterTest\\:\\:getActualValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Export/Spreadsheet/CellFormatter/DurationFormatterTest.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\DurationFormatterTest\\:\\:getExpectedValue\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Export/Spreadsheet/CellFormatter/DurationFormatterTest.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\TimeFormatterTest\\:\\:getActualValue\\(\\) has no return type specified\\.$#"
|
message: "#^Method App\\\\Tests\\\\Export\\\\Spreadsheet\\\\CellFormatter\\\\TimeFormatterTest\\:\\:getActualValue\\(\\) has no return type specified\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|||||||
@@ -164,7 +164,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Délka slotu pro týdenní a denní zobrazení (formát: hh:mm:ss)</target>
|
<target>Délka slotu pro týdenní a denní zobrazení</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
||||||
<source>timesheet.rules.lockdown_grace_period</source>
|
<source>timesheet.rules.lockdown_grace_period</source>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Pladsvarighed for uge- og dagstilstande (format: hh:mm:ss)</target>
|
<target>Pladsvarighed for uge- og dagstilstande</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Slotdauer für Wochen- und Tagesansicht (Format: hh:mm:ss)</target>
|
<target>Slotdauer für Wochen- und Tagesansicht</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target state="translated">Slotdauer für Wochen- und Tagesansicht (Format: hh:mm:ss)</target>
|
<target state="translated">Slotdauer für Wochen- und Tagesansicht</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="o9mON14" resname="theme.branding.logo">
|
<trans-unit id="o9mON14" resname="theme.branding.logo">
|
||||||
<source>theme.branding.logo</source>
|
<source>theme.branding.logo</source>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Slot duration for week- and day view (format: hh:mm:ss)</target>
|
<target>Slot duration for week- and day view</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Daŭro de tempo-bloko por semajna kaj taga vidoj (formo: hh:mm:ss)</target>
|
<target>Daŭro de tempo-bloko por semajna kaj taga vidoj</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -176,7 +176,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Unidad mínima de tiempo para las vistas semanal y diaria (formato: hh:mm:ss)</target>
|
<target>Unidad mínima de tiempo para las vistas semanal y diaria</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="yBBvkfb" resname="timesheet.rules.allow_zero_duration">
|
<trans-unit id="yBBvkfb" resname="timesheet.rules.allow_zero_duration">
|
||||||
<source>timesheet.rules.allow_zero_duration</source>
|
<source>timesheet.rules.allow_zero_duration</source>
|
||||||
|
|||||||
@@ -164,7 +164,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Astearen eta egunaren ikuspegiaren zirrikituaren iraupena (formatua: hh:mm:ss)</target>
|
<target>Astearen eta egunaren ikuspegiaren zirrikituaren iraupena</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
||||||
<source>timesheet.rules.lockdown_grace_period</source>
|
<source>timesheet.rules.lockdown_grace_period</source>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target state="translated">مدت زمان اسلات برای نمای هفته و روز (قالب: hh:mm:ss)</target>
|
<target state="translated">مدت زمان اسلات برای نمای هفته و روز</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target state="translated">Paikan kesto viikko- ja päivänäkymälle (muoto: hh:mm:ss)</target>
|
<target state="translated">Paikan kesto viikko- ja päivänäkymälle</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Durée de a vue en semaine et en jour (format : hh:mm:ss)</target>
|
<target>Durée de a vue en semaine et en jour</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
||||||
<source>timesheet.rules.lockdown_grace_period</source>
|
<source>timesheet.rules.lockdown_grace_period</source>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration" xml:space="preserve" approved="yes">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration" xml:space="preserve" approved="yes">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target state="final">משך זמן העבודה בתצוגת שבוע ויום (תבנית: hh:mm:ss)</target>
|
<target state="final">משך זמן העבודה בתצוגת שבוע ויום</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target state="translated">Trajanje vremenskog razdoblja za prikaz tjedna i dana (format: hh:mm:ss)</target>
|
<target state="translated">Trajanje vremenskog razdoblja za prikaz tjedna i dana</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Bejegyzés hossza heti és napi nézetben (formátum: hh:mm:ss)</target>
|
<target>Bejegyzés hossza heti és napi nézetben</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="_0OBfZf" resname="timesheet.default_begin">
|
<trans-unit id="_0OBfZf" resname="timesheet.default_begin">
|
||||||
<source>timesheet.default_begin</source>
|
<source>timesheet.default_begin</source>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration" approved="yes">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration" approved="yes">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target state="final">Durata slot per la vista settimanale e giornaliera (hh:mm:ss)</target>
|
<target state="final">Durata slot per la vista settimanale e giornaliera</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding" approved="yes">
|
<trans-unit id="nwuLBP4" resname="branding" approved="yes">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -152,7 +152,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>주별 및 일별 보기의 슬롯 지속시간 (형식: hh:mm:ss)</target>
|
<target>주별 및 일별 보기의 슬롯 지속시간</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
||||||
<source>timesheet.rules.lockdown_grace_period</source>
|
<source>timesheet.rules.lockdown_grace_period</source>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Slotduur voor week- en dagweergave (formaat: hh:mm:ss)</target>
|
<target>Slotduur voor week- en dagweergave</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -152,7 +152,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Duração da célula para a visualização da semana e do dia (formato: hh:mm:ss)</target>
|
<target>Duração da célula para a visualização da semana e do dia</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
||||||
<source>timesheet.rules.lockdown_grace_period</source>
|
<source>timesheet.rules.lockdown_grace_period</source>
|
||||||
|
|||||||
@@ -152,7 +152,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Duração do slot para a exibição da semana e do dia (formato: hh:mm:ss)</target>
|
<target>Duração do slot para a exibição da semana e do dia</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
<trans-unit id="RLgv62J" resname="timesheet.rules.lockdown_grace_period">
|
||||||
<source>timesheet.rules.lockdown_grace_period</source>
|
<source>timesheet.rules.lockdown_grace_period</source>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Durata slotului pentru vizualizarea săptămânii și a zilei (format: hh:mm:ss)</target>
|
<target>Durata slotului pentru vizualizarea săptămânii și a zilei</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target>Trvanie slotu pre týždňové- a denné zobrazenie (formát: hh:mm:ss)</target>
|
<target>Trvanie slotu pre týždňové- a denné zobrazenie</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6nayLDB" resname="calendar.visibleHours.end">
|
<trans-unit id="6nayLDB" resname="calendar.visibleHours.end">
|
||||||
<source>calendar.visibleHours.end</source>
|
<source>calendar.visibleHours.end</source>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
<trans-unit id="BYy.c1V" resname="calendar.slot_duration">
|
||||||
<source>calendar.slot_duration</source>
|
<source>calendar.slot_duration</source>
|
||||||
<target state="translated">Тривалість інтервалу для перегляду тижня та дня (формат: hh:mm:ss)</target>
|
<target state="translated">Тривалість інтервалу для перегляду тижня та дня</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="nwuLBP4" resname="branding">
|
<trans-unit id="nwuLBP4" resname="branding">
|
||||||
<source>branding</source>
|
<source>branding</source>
|
||||||
|
|||||||
Reference in New Issue
Block a user