Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d3e46cce2 | ||
|
|
1c3aac78f6 | ||
|
|
b1fb300a71 | ||
|
|
a4d658b821 | ||
|
|
4920ea5075 | ||
|
|
03dbbdc5ba | ||
|
|
023048e7c0 | ||
|
|
24778d3ffb |
2
.github/workflows/testing.yaml
vendored
2
.github/workflows/testing.yaml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
run: APP_ENV=dev bin/console kimai:reload -n
|
||||
|
||||
- name: Check codestyles
|
||||
run: PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run --verbose --config=.php-cs-fixer.dist.php --using-cache=no --show-progress=none --format=checkstyle | cs2pr
|
||||
run: vendor/bin/php-cs-fixer fix --dry-run --verbose --config=.php-cs-fixer.dist.php --using-cache=no --show-progress=none --format=checkstyle | cs2pr
|
||||
|
||||
- name: Run PHPStan for application
|
||||
run: vendor/bin/phpstan analyse -c phpstan.neon --no-progress --error-format=checkstyle | cs2pr
|
||||
|
||||
@@ -8,6 +8,7 @@ file that was distributed with this source code.
|
||||
COMMENT;
|
||||
|
||||
$fixer = new PhpCsFixer\Config();
|
||||
$fixer->setUnsupportedPhpVersionAllowed(true);
|
||||
$fixer
|
||||
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
||||
->setRiskyAllowed(true)
|
||||
|
||||
@@ -73,21 +73,6 @@ export default class KimaiCalendar {
|
||||
/** @type {KimaiAlert} 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
|
||||
// https://fullcalendar.io/docs/buttonIcons
|
||||
BootstrapTheme.prototype.classes = {
|
||||
@@ -120,7 +105,8 @@ export default class KimaiCalendar {
|
||||
esLocale, euLocale, faLocale, fiLocale, frLocale, heLocale, hrLocale, huLocale, itLocale, jaLocale, koLocale,
|
||||
nbLocale, nlLocale, plLocale, ptLocale, ptBrLocale, roLocale, ruLocale, skLocale, svLocale, trLocale, zhLocale, viLocale ],
|
||||
plugins: [ bootstrap5Plugin, dayGridPlugin, timeGridPlugin, googlePlugin, iCalendarPlugin, interactionPlugin ],
|
||||
initialView: initialView,
|
||||
initialView: this.toInternalViewName(this.options['initialView']),
|
||||
initialDate: this.options['initialDate'],
|
||||
// https://fullcalendar.io/docs/theming
|
||||
themeSystem: 'bootstrap5',
|
||||
// https://fullcalendar.io/docs/headerToolbar
|
||||
@@ -155,8 +141,9 @@ export default class KimaiCalendar {
|
||||
slotMinTime: this.options['timeframeBegin'] + ':00',
|
||||
slotMaxTime: this.options['timeframeEnd'] === '23:59' ? '24:00:00' : (this.options['timeframeEnd'] + ':59'),
|
||||
|
||||
// auto calculation seems to do the better job, therefor deactivated
|
||||
//slotLabelInterval: this.options['slotDuration'],
|
||||
// deactivate for auto calculation, which does a good job.
|
||||
// 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
|
||||
defaultTimedEventDuration: this.options['slotDuration'],
|
||||
@@ -172,14 +159,20 @@ export default class KimaiCalendar {
|
||||
// once we can configure working days
|
||||
// hiddenDays: [ 2, 4 ]
|
||||
|
||||
// when we support holidays and other full day events
|
||||
// allDaySlot: false,
|
||||
// dropAccept
|
||||
|
||||
dayMaxEventRows: true,
|
||||
eventMaxStack: 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: {
|
||||
dayGrid: {
|
||||
dayMaxEventRows: this.options['dayLimit']
|
||||
@@ -535,6 +528,44 @@ export default class KimaiCalendar {
|
||||
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
|
||||
* @return {boolean}
|
||||
@@ -718,16 +749,16 @@ export default class KimaiCalendar {
|
||||
}
|
||||
|
||||
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]) {
|
||||
durations[dateStr] = 0;
|
||||
}
|
||||
|
||||
// absences or public holidays are all day
|
||||
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');
|
||||
durations[dateStr] += duration;
|
||||
}
|
||||
|
||||
613
composer.lock
generated
613
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,13 @@
|
||||
'rtl' => true,
|
||||
'translation' => true,
|
||||
],
|
||||
'bg' =>
|
||||
[
|
||||
'date' => 'd.MM.y',
|
||||
'time' => 'HH:mm',
|
||||
'rtl' => false,
|
||||
'translation' => true,
|
||||
],
|
||||
'cs' =>
|
||||
[
|
||||
'date' => 'dd.MM.y',
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
parameters:
|
||||
locale: en
|
||||
# can be regenerated with: bin/console kimai:reset:locales
|
||||
kimai_locales: ['ar', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'ta', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_CZ', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_ES', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_FR', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GS', 'en_GU', 'en_GY', 'en_HK', 'en_HU', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_IT', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NO', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PL', 'en_PN', 'en_PR', 'en_PT', 'en_PW', 'en_RH', 'en_RO', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SK', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN']
|
||||
kimai_locales: ['ar', 'bg', 'cs', 'da', 'de', 'de_CH', 'el', 'en', 'eo', 'es', 'eu', 'fa', 'fi', 'fo', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'nb_NO', 'nl', 'pa', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sv', 'ta', 'tr', 'uk', 'vi', 'zh_CN', 'zh_Hant', 'zh_Hant_TW', 'cs_CZ', 'da_DK', 'da_GL', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'el_CY', 'el_GR', 'en_AE', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_CZ', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_ES', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_FR', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GS', 'en_GU', 'en_GY', 'en_HK', 'en_HU', 'en_ID', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_IT', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MV', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NH', 'en_NL', 'en_NO', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PL', 'en_PN', 'en_PR', 'en_PT', 'en_PW', 'en_RH', 'en_RO', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SK', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'eu_ES', 'fa_AF', 'fa_IR', 'fi_FI', 'fo_DK', 'fo_FO', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'he_IL', 'hr_BA', 'hr_HR', 'hu_HU', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja_JP', 'ko_CN', 'ko_KP', 'ko_KR', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'pl_PL', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'ro_MD', 'ro_RO', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'sk_SK', 'sv_AX', 'sv_FI', 'sv_SE', 'tr_CY', 'tr_TR', 'uk_UA', 'vi_VN']
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
public: false # Allows optimizing the container by removing unused services; this also means
|
||||
# fetching services directly from the container via $container->get() won't work.
|
||||
# The best practice is to be explicit about your dependencies anyway.
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
bind:
|
||||
$projectDirectory: '%kernel.project_dir%'
|
||||
$kernelEnvironment: '%kernel.environment%'
|
||||
|
||||
1
kimai.sh
1
kimai.sh
@@ -22,6 +22,7 @@ function update_kimai() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf var/sessions/ 2>&1
|
||||
rm -rf var/cache/* 2>&1
|
||||
git fetch --tags
|
||||
git checkout "$VERSION"
|
||||
|
||||
@@ -663,11 +663,6 @@ parameters:
|
||||
count: 1
|
||||
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\\.$#"
|
||||
count: 1
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -54,7 +54,7 @@
|
||||
"calendar": {
|
||||
"js": [
|
||||
"/build/runtime.6c399d29.js",
|
||||
"/build/calendar.f4379767.js"
|
||||
"/build/calendar.d82e420f.js"
|
||||
],
|
||||
"css": [
|
||||
"/build/calendar.d757753e.css"
|
||||
@@ -92,7 +92,7 @@
|
||||
"/build/invoice-pdf.26d98626.js": "sha384-gwNzQiU1y6qU/M9DPGiNW0MVZkLctEHk37sCES2X9ov+zugEaDABdkMjKBYOC9lz",
|
||||
"/build/invoice-pdf.2b749265.css": "sha384-DXXgkz2WWnrWnfBnXX5fmfPQSPb98upMnWxYKwTGYS04EhrPIWfDCutB2unIrWh7",
|
||||
"/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/dashboard.632f98fb.js": "sha384-PlHarP53f8b+47VZvbQw3LURA2vODFf7UMwpnktvukrhswaHtx93cx2M1BtO56JH",
|
||||
"/build/dashboard.b7129fa1.css": "sha384-2nn5hLA+3YedgHYBpge62S8Losj8aoPwK9Zk9EvN1xEYatvOUQ7H3rIR2UUJAGOS",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"build/invoice-pdf.js": "/build/invoice-pdf.26d98626.js",
|
||||
"build/chart.js": "/build/chart.62631acc.js",
|
||||
"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.js": "/build/dashboard.632f98fb.js",
|
||||
"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;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ final class RegenerateLocalesCommand extends Command
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private array $noRegionCode = ['ar', 'id', 'pa', 'sl', 'ca', 'ta'];
|
||||
private array $noRegionCode = ['ar', 'id', 'pa', 'sl', 'ca', 'ta', 'bg'];
|
||||
/**
|
||||
* A list of locales that will be activated no matter if translation files exist for them.
|
||||
*
|
||||
@@ -153,6 +153,9 @@ final class RegenerateLocalesCommand extends Command
|
||||
$settings['time'] = str_replace('Bh', 'H', $settings['time']);
|
||||
}
|
||||
|
||||
// BULGARIAN: postfix of d.MM.y \'г\'. causes problems in several areas
|
||||
$settings['date'] = str_replace("\u{202f}'г'.", '', $settings['date']);
|
||||
|
||||
// KOREAN: time format failed parsing - see https://github.com/kimai/kimai/issues/4402
|
||||
// Special case where time-patterns start with A / a => this will lead to an error
|
||||
// \DateTimeImmutable::getLastErrors() => Meridian can only come after an hour has been found
|
||||
|
||||
@@ -17,11 +17,11 @@ final class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.38.0';
|
||||
public const VERSION = '2.39.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 23800;
|
||||
public const VERSION_ID = 23900;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -9,15 +9,18 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Calendar\CalendarQuery;
|
||||
use App\Calendar\CalendarService;
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\User;
|
||||
use App\Form\CalendarForm;
|
||||
use App\Form\Toolbar\CalendarToolbarForm;
|
||||
use App\Form\Type\CalendarViewType;
|
||||
use App\Timesheet\TrackingModeService;
|
||||
use App\Utils\PageSetup;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
@@ -35,34 +38,41 @@ final class CalendarController extends AbstractController
|
||||
#[Route(path: '/{profile}', name: 'calendar_user', methods: ['GET'])]
|
||||
public function userCalendar(Request $request): Response
|
||||
{
|
||||
$form = null;
|
||||
$profile = $this->getUser();
|
||||
$currentUser = $this->getUser();
|
||||
$profile = $currentUser;
|
||||
$canChangeUser = $this->isGranted('view_other_timesheet');
|
||||
|
||||
if ($this->isGranted('view_other_timesheet')) {
|
||||
$form = $this->createFormForGetRequest(CalendarForm::class, ['user' => $profile], [
|
||||
'action' => $this->generateUrl('calendar'),
|
||||
]);
|
||||
$query = new CalendarQuery();
|
||||
$query->setUser($profile);
|
||||
$query->setDate($this->getDateTimeFactory($profile)->create());
|
||||
|
||||
$form->handleRequest($request);
|
||||
$defaultView = CalendarViewType::DEFAULT_VIEW;
|
||||
$userView = $profile->getPreference('calendar_initial_view')?->getValue();
|
||||
if ($userView !== null) {
|
||||
$defaultView = (string) $userView;
|
||||
}
|
||||
$query->setView($defaultView);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$values = $form->getData();
|
||||
if ($values['user'] instanceof User) {
|
||||
$profile = $values['user'];
|
||||
}
|
||||
}
|
||||
$form = $this->createFormForGetRequest(CalendarToolbarForm::class, $query, [
|
||||
'action' => $this->generateUrl('calendar'),
|
||||
'change_user' => $canChangeUser,
|
||||
]);
|
||||
|
||||
$form = $form->createView();
|
||||
$form->submit($request->query->all(), false);
|
||||
|
||||
// hide if the current user is the only available one
|
||||
if (\count($form->offsetGet('user')->vars['choices']) < 2) {
|
||||
$form = null;
|
||||
$profile = $this->getUser();
|
||||
}
|
||||
if ($query->getUser() === null) {
|
||||
$query->setUser($currentUser);
|
||||
}
|
||||
|
||||
/** @var User $profile */
|
||||
$profile = $query->getUser();
|
||||
|
||||
if ($currentUser !== $profile && !$canChangeUser) {
|
||||
throw new AccessDeniedException('User is not allowed to see other users calendar');
|
||||
}
|
||||
|
||||
$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
|
||||
$defaultStart = null;
|
||||
@@ -89,7 +99,9 @@ final class CalendarController extends AbstractController
|
||||
|
||||
return $this->render('calendar/user.html.twig', [
|
||||
'page_setup' => $page,
|
||||
'form' => $form,
|
||||
'initial_view' => $query->getView(),
|
||||
'initial_date' => $query->getDate(),
|
||||
'form' => $form->createView(),
|
||||
'user' => $profile,
|
||||
'config' => $config,
|
||||
'dragAndDrop' => $dragAndDrop,
|
||||
|
||||
@@ -83,8 +83,25 @@ final class ContractController extends AbstractController
|
||||
$boxConfiguration->setDecimal(false);
|
||||
$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', [
|
||||
'withWorkHourConfiguration' => $profile->hasWorkHourConfiguration(),
|
||||
'days' => $days,
|
||||
'withWorkHourConfiguration' => $hasConfiguration,
|
||||
'box_configuration' => $boxConfiguration,
|
||||
'page_setup' => $page,
|
||||
'decimal' => $boxConfiguration->isDecimal(),
|
||||
|
||||
@@ -37,6 +37,7 @@ use App\Validator\Constraints\ColorChoices;
|
||||
use App\Validator\Constraints\DateTimeFormat;
|
||||
use App\Validator\Constraints\TimeFormat;
|
||||
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\CurrencyType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
@@ -587,7 +588,8 @@ final class SystemConfigurationController extends AbstractController
|
||||
->setConstraints([new NotBlank(), new TimeFormat()]),
|
||||
(new Configuration('calendar.slot_duration'))
|
||||
->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()]),
|
||||
(new Configuration('calendar.dragdrop_amount'))
|
||||
->setTranslationDomain('system-configuration')
|
||||
|
||||
@@ -365,9 +365,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
}
|
||||
|
||||
if ($dto->isRecalculateRates()) {
|
||||
$timesheet->setFixedRate(null);
|
||||
$timesheet->setHourlyRate(null);
|
||||
$timesheet->setInternalRate(null);
|
||||
$timesheet->resetRates();
|
||||
$execute = true;
|
||||
} elseif (null !== $dto->getFixedRate()) {
|
||||
$timesheet->setFixedRate($dto->getFixedRate());
|
||||
|
||||
@@ -19,7 +19,7 @@ use App\Timesheet\DateTimeFactory;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
@@ -112,7 +112,7 @@ class CustomerStatisticService
|
||||
$qb = $this->timesheetRepository->createQueryBuilder('t');
|
||||
$qb
|
||||
->select('IDENTITY(p.customer) AS id')
|
||||
->join(Project::class, 'p', Query\Expr\Join::WITH, 't.project = p.id')
|
||||
->join(Project::class, 'p', Join::WITH, 't.project = p.id')
|
||||
->addSelect('COALESCE(SUM(t.duration), 0) as duration')
|
||||
->addSelect('COALESCE(SUM(t.rate), 0) as rate')
|
||||
->addSelect('COALESCE(SUM(t.internalRate), 0) as internalRate')
|
||||
|
||||
@@ -35,7 +35,7 @@ final class TimesheetApiEditForm extends TimesheetEditForm
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
$data = $event->getData();
|
||||
if (\array_key_exists('billable', $data)) {
|
||||
$data['billableMode'] = Timesheet::BILLABLE_AUTOMATIC;
|
||||
|
||||
@@ -27,7 +27,7 @@ trait ColorTrait
|
||||
// this code exists only for backward compatibility
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SET_DATA,
|
||||
function (FormEvent $event) use ($required) {
|
||||
function (FormEvent $event) use ($required): void {
|
||||
if (!$event->getForm()->getConfig()->hasOption('choices')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ trait FormTrait
|
||||
// replaces the project select after submission, to make sure only projects for the selected customer are displayed
|
||||
$builder->addEventListener(
|
||||
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 */
|
||||
$data = $event->getData();
|
||||
$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
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($options) {
|
||||
function (FormEvent $event) use ($options): void {
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $event->getData();
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ final class TimesheetMultiUpdate extends AbstractType
|
||||
// TODO replace me with FormTrait
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($project, $customer) {
|
||||
function (FormEvent $event) use ($project, $customer): void {
|
||||
$data = $event->getData();
|
||||
$customer = isset($data['customer']) && !empty($data['customer']) ? $data['customer'] : null;
|
||||
$project = isset($data['project']) && !empty($data['project']) ? $data['project'] : $project;
|
||||
@@ -126,7 +126,7 @@ final class TimesheetMultiUpdate extends AbstractType
|
||||
// TODO replace me with FormTrait
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($activityOptions) {
|
||||
function (FormEvent $event) use ($activityOptions): void {
|
||||
$data = $event->getData();
|
||||
if (!isset($data['project']) || empty($data['project'])) {
|
||||
return;
|
||||
|
||||
@@ -188,7 +188,7 @@ class TimesheetEditForm extends AbstractType
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::POST_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
$begin = $timesheet->getBegin();
|
||||
@@ -203,7 +203,7 @@ class TimesheetEditForm extends AbstractType
|
||||
// map single fields to original datetime object
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var Timesheet $data */
|
||||
$data = $event->getData();
|
||||
|
||||
@@ -243,7 +243,7 @@ class TimesheetEditForm extends AbstractType
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::POST_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var Timesheet|null $data */
|
||||
$data = $event->getData();
|
||||
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
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
$oldEnd = $timesheet->getEnd();
|
||||
@@ -334,7 +334,7 @@ class TimesheetEditForm extends AbstractType
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::POST_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var Timesheet|null $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
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
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) use ($forceApply) {
|
||||
function (FormEvent $event) use ($forceApply): void {
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
|
||||
|
||||
@@ -7,17 +7,24 @@
|
||||
* 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 Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class CalendarForm extends AbstractType
|
||||
final class CalendarToolbarForm extends AbstractType
|
||||
{
|
||||
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, [
|
||||
'required' => false,
|
||||
'attr' => ['onchange' => 'this.form.submit()']
|
||||
@@ -28,7 +35,9 @@ final class CalendarForm extends AbstractType
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'method' => 'GET',
|
||||
'change_user' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ trait ToolbarFormTrait
|
||||
|
||||
$builder->addEventListener(
|
||||
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 */
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, CustomerType::class, array_merge([
|
||||
@@ -187,7 +187,7 @@ trait ToolbarFormTrait
|
||||
|
||||
$builder->addEventListener(
|
||||
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 */
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, ProjectType::class, array_merge([
|
||||
@@ -266,7 +266,7 @@ trait ToolbarFormTrait
|
||||
|
||||
$builder->addEventListener(
|
||||
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 */
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, ActivityType::class, array_merge($activityOptions, [
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
namespace App\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
@@ -20,12 +22,28 @@ final class CalendarViewType extends AbstractType
|
||||
{
|
||||
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
|
||||
{
|
||||
$choices = [
|
||||
'month' => 'month',
|
||||
'agendaWeek' => 'agendaWeek',
|
||||
'agendaDay' => 'agendaDay',
|
||||
'agendaWeek' => 'week',
|
||||
'agendaDay' => 'day',
|
||||
];
|
||||
|
||||
$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(
|
||||
FormEvents::PRE_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var MetaTableTypeInterface $definition */
|
||||
$definition = $event->getData();
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ final class ExportColumnsType extends AbstractType
|
||||
{
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
$data = $event->getData();
|
||||
if (\is_array($data)) {
|
||||
$this->ordered = $data; // @phpstan-ignore assign.propertyType
|
||||
@@ -156,7 +156,7 @@ final class ExportColumnsType extends AbstractType
|
||||
);
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
$event->setData($this->ordered);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ final class MetaFieldsCollectionType extends AbstractType
|
||||
{
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SET_DATA,
|
||||
function (FormEvent $event) use ($options) {
|
||||
function (FormEvent $event) use ($options): void {
|
||||
/** @var ArrayCollection<MetaTableTypeInterface> $collection */
|
||||
$collection = $event->getData();
|
||||
foreach ($collection as $collectionItem) {
|
||||
|
||||
@@ -53,7 +53,7 @@ final class QuickEntryTimesheetType extends AbstractType
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::POST_SET_DATA,
|
||||
function (FormEvent $event) use ($durationOptions) {
|
||||
function (FormEvent $event) use ($durationOptions): void {
|
||||
/** @var Timesheet|null $data */
|
||||
$data = $event->getData();
|
||||
if (null === $data || $data->isRunning()) {
|
||||
@@ -89,7 +89,7 @@ final class QuickEntryTimesheetType extends AbstractType
|
||||
// make sure that duration is mapped back to end field
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var Timesheet $data */
|
||||
$data = $event->getData();
|
||||
$duration = $data->getDuration(false);
|
||||
|
||||
@@ -39,7 +39,7 @@ final class QuickEntryWeekType extends AbstractType
|
||||
|
||||
$builder->add('project', ProjectType::class, $projectOptions);
|
||||
|
||||
$projectFunction = function (FormEvent $event) use ($projectOptions) {
|
||||
$projectFunction = function (FormEvent $event) use ($projectOptions): void {
|
||||
/** @var QuickEntryModel|null $data */
|
||||
$data = $event->getData();
|
||||
if ($data === null || $data->getProject() === null) {
|
||||
@@ -62,7 +62,7 @@ final class QuickEntryWeekType extends AbstractType
|
||||
|
||||
$builder->add('activity', ActivityType::class, $activityOptions);
|
||||
|
||||
$activityFunction = function (FormEvent $event) use ($activityOptions) {
|
||||
$activityFunction = function (FormEvent $event) use ($activityOptions): void {
|
||||
/** @var QuickEntryModel|null $data */
|
||||
$data = $event->getData();
|
||||
if ($data === null || $data->getActivity() === null) {
|
||||
@@ -77,7 +77,7 @@ final class QuickEntryWeekType extends AbstractType
|
||||
$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
|
||||
$activityPreSubmitFunction = function (FormEvent $event) use ($activityOptions) {
|
||||
$activityPreSubmitFunction = function (FormEvent $event) use ($activityOptions): void {
|
||||
$data = $event->getData();
|
||||
|
||||
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) {
|
||||
$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
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var QuickEntryModel $data */
|
||||
$data = $event->getData();
|
||||
$newRecords = $data->getNewTimesheet();
|
||||
|
||||
@@ -31,7 +31,7 @@ final class SystemConfigurationType extends AbstractType
|
||||
{
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var Configuration $preference */
|
||||
$preference = $event->getData();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ final class TagsSelectType extends AbstractType
|
||||
|
||||
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 */
|
||||
$tagIds = $event->getData();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ final class UserPreferenceType extends AbstractType
|
||||
{
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var UserPreference $preference */
|
||||
$preference = $event->getData();
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ final class UserPreferencesCollectionType extends AbstractType
|
||||
{
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
function (FormEvent $event): void {
|
||||
/** @var ArrayCollection<UserPreference> $collection */
|
||||
$collection = $event->getData();
|
||||
foreach ($collection as $collectionItem) {
|
||||
|
||||
@@ -34,7 +34,7 @@ final class ProjectDetailsForm extends AbstractType
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($projectOptions) {
|
||||
function (FormEvent $event) use ($projectOptions): void {
|
||||
$data = $event->getData();
|
||||
if (isset($data['project']) && !empty($data['project'])) {
|
||||
$projectId = $data['project'];
|
||||
|
||||
@@ -33,7 +33,7 @@ use Doctrine\ORM\Query\Expr\Andx;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<Activity>
|
||||
* @extends EntityRepository<Activity>
|
||||
*/
|
||||
class ActivityRepository extends EntityRepository
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\Entity\CustomerRate;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<CustomerRate>
|
||||
* @extends EntityRepository<CustomerRate>
|
||||
*/
|
||||
class CustomerRateRepository extends EntityRepository
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Entity\ExportTemplate;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<ExportTemplate>
|
||||
* @extends EntityRepository<ExportTemplate>
|
||||
*/
|
||||
class ExportTemplateRepository extends EntityRepository
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<Invoice>
|
||||
* @extends EntityRepository<Invoice>
|
||||
*/
|
||||
class InvoiceRepository extends EntityRepository
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<InvoiceTemplate>
|
||||
* @extends EntityRepository<InvoiceTemplate>
|
||||
*/
|
||||
class InvoiceTemplateRepository extends EntityRepository
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\Entity\ProjectRate;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<ProjectRate>
|
||||
* @extends EntityRepository<ProjectRate>
|
||||
*/
|
||||
class ProjectRateRepository extends EntityRepository
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ use Doctrine\ORM\Query\Expr\Andx;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<Project>
|
||||
* @extends EntityRepository<Project>
|
||||
*/
|
||||
class ProjectRepository extends EntityRepository
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\Entity\RolePermission;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<RolePermission>
|
||||
* @extends EntityRepository<RolePermission>
|
||||
*/
|
||||
class RolePermissionRepository extends EntityRepository
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
|
||||
/**
|
||||
* @extends \Doctrine\ORM\EntityRepository<Role>
|
||||
* @extends EntityRepository<Role>
|
||||
* @method Role[] findAll()
|
||||
*/
|
||||
class RoleRepository extends EntityRepository
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace App\Validator\Constraints;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Timesheet as TimesheetEntity;
|
||||
use App\Utils\Duration;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
@@ -64,7 +65,7 @@ final class TimesheetLongRunningValidator extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
$format = new \App\Utils\Duration();
|
||||
$format = new Duration();
|
||||
$hours = $format->format($maxMinutes * 60);
|
||||
|
||||
$this->context->buildViolation($constraint->message)
|
||||
|
||||
@@ -18,7 +18,7 @@ final class WorkingTimeCalculatorNone implements WorkingTimeCalculator
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,19 @@
|
||||
{% if form is not null %}
|
||||
{% embed '@theme/embeds/card.html.twig' %}
|
||||
{% block box_body %}
|
||||
{{ form_start(form) }}
|
||||
{{ form_start(form, {attr: {id: 'calendar-form'}}) }}
|
||||
{% 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 %}
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
@@ -92,15 +102,33 @@
|
||||
document.addEventListener('kimai.timesheetUpdate', 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) {
|
||||
const kimai = event.detail.kimai;
|
||||
|
||||
let calendarOptions = {
|
||||
initialDate: '{{ initial_date|report_date }}',
|
||||
dragdrop: {
|
||||
container: '.external-events',
|
||||
items: '.external-event',
|
||||
},
|
||||
initialView: '{{ app.user.getPreferenceValue('calendar_initial_view') }}',
|
||||
initialView: '{{ initial_view }}',
|
||||
translations: {
|
||||
customer: '{{ 'customer'|trans }}',
|
||||
project: '{{ 'project'|trans }}',
|
||||
|
||||
@@ -222,6 +222,38 @@
|
||||
{% endembed %}
|
||||
{% 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 %}
|
||||
{{ render(controller(controller, {'year': year, 'boxConfiguration': box_configuration})) }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -150,6 +150,25 @@
|
||||
</div>
|
||||
{%- 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 -%}
|
||||
{%- set attr = attr|merge({'pattern': time_format|pattern, 'autocomplete': 'off', 'data-timepicker': 'on', 'data-format': js_format, 'placeholder': time_format}) -%}
|
||||
<div class="input-group">
|
||||
|
||||
@@ -14,10 +14,9 @@ use App\Tests\DataFixtures\ActivityFixtures;
|
||||
use App\Tests\DataFixtures\CustomerFixtures;
|
||||
use App\Tests\DataFixtures\ProjectFixtures;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class ActionsControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
public function testIsSecure(): void
|
||||
|
||||
@@ -21,13 +21,13 @@ use App\Repository\ActivityRateRepository;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Repository\Query\VisibilityInterface;
|
||||
use App\Tests\Mocks\ActivityTestMetaFieldSubscriberMock;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class ActivityControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
use RateControllerTestTrait;
|
||||
@@ -140,9 +140,7 @@ class ActivityControllerTest extends APIControllerBaseTestCase
|
||||
return [$project, $project2, $activity1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getCollectionTestData
|
||||
*/
|
||||
#[DataProvider('getCollectionTestData')]
|
||||
public function testGetCollection($url, $project, $parameters, $expected): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
|
||||
@@ -11,10 +11,9 @@ namespace App\Tests\API;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Tests\Controller\AbstractControllerBaseTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class ApiDocControllerTest extends AbstractControllerBaseTestCase
|
||||
{
|
||||
public function testIsSecure(): void
|
||||
|
||||
@@ -13,12 +13,11 @@ use App\API\Authentication\AccessTokenHandler;
|
||||
use App\Entity\AccessToken;
|
||||
use App\Entity\User;
|
||||
use App\Repository\AccessTokenRepository;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
|
||||
/**
|
||||
* @covers \App\API\Authentication\AccessTokenHandler
|
||||
*/
|
||||
#[CoversClass(AccessTokenHandler::class)]
|
||||
class AccessTokenHandlerTest extends TestCase
|
||||
{
|
||||
private function getSut(?AccessToken $accessToken = null): AccessTokenHandler
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace App\Tests\API\Authentication;
|
||||
use App\API\Authentication\TokenAuthenticator;
|
||||
use App\Entity\User;
|
||||
use App\Repository\ApiUserRepository;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
@@ -21,10 +23,8 @@ use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationExc
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
|
||||
|
||||
/**
|
||||
* @covers \App\API\Authentication\TokenAuthenticator
|
||||
* @group legacy
|
||||
*/
|
||||
#[CoversClass(TokenAuthenticator::class)]
|
||||
#[Group('legacy')]
|
||||
class TokenAuthenticatorTest extends TestCase
|
||||
{
|
||||
private function getSut(bool $verify = true): TokenAuthenticator
|
||||
|
||||
@@ -11,14 +11,14 @@ namespace App\Tests\API;
|
||||
|
||||
use App\DataFixtures\UserFixtures;
|
||||
use App\Entity\User;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* These tests make sure, that the deprecated API login with X-AUTH-USER and X-AUTH-TOKEN still works.
|
||||
*
|
||||
* @group legacy
|
||||
* @group integration
|
||||
* Make sure that the deprecated API login with X-AUTH-USER and X-AUTH-TOKEN still works.
|
||||
*/
|
||||
#[Group('legacy')]
|
||||
#[Group('integration')]
|
||||
class AuthenticationTest extends APIControllerBaseTestCase
|
||||
{
|
||||
public function testPinIsSecure(): void
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
namespace App\Tests\API;
|
||||
|
||||
use App\Entity\User;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class ConfigurationControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
public function testIsTimesheetSecure(): void
|
||||
|
||||
@@ -21,13 +21,12 @@ use App\Entity\User;
|
||||
use App\Repository\CustomerRateRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Tests\Mocks\CustomerTestMetaFieldSubscriberMock;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class CustomerControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
use RateControllerTestTrait;
|
||||
|
||||
@@ -12,12 +12,11 @@ namespace App\Tests\API;
|
||||
use App\Entity\ExportTemplate;
|
||||
use App\Entity\User;
|
||||
use App\Repository\ExportTemplateRepository;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class ExportControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
private function importExportTemplate(): ExportTemplate
|
||||
|
||||
@@ -12,10 +12,9 @@ namespace App\Tests\API;
|
||||
use App\Entity\Invoice;
|
||||
use App\Entity\User;
|
||||
use App\Tests\DataFixtures\InvoiceFixtures;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class InvoiceControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -10,11 +10,10 @@
|
||||
namespace App\Tests\API\Model;
|
||||
|
||||
use App\API\Model\PageAction;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\API\Model\PageAction
|
||||
*/
|
||||
#[CoversClass(PageAction::class)]
|
||||
class PageActionTest extends TestCase
|
||||
{
|
||||
public function testEmptySettings(): void
|
||||
|
||||
@@ -10,11 +10,10 @@
|
||||
namespace App\Tests\API\Model;
|
||||
|
||||
use App\API\Model\TimesheetConfig;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\API\Model\TimesheetConfig
|
||||
*/
|
||||
#[CoversClass(TimesheetConfig::class)]
|
||||
class TimesheetConfigTest extends TestCase
|
||||
{
|
||||
public function testSetter(): void
|
||||
|
||||
@@ -11,11 +11,10 @@ namespace App\Tests\API\Model;
|
||||
|
||||
use App\API\Model\Version;
|
||||
use App\Constants;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\API\Model\Version
|
||||
*/
|
||||
#[CoversClass(Version::class)]
|
||||
class VersionTest extends TestCase
|
||||
{
|
||||
public function testValues(): void
|
||||
|
||||
@@ -10,11 +10,10 @@
|
||||
namespace App\Tests\API;
|
||||
|
||||
use App\API\NotFoundException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\API\NotFoundException
|
||||
*/
|
||||
#[CoversClass(NotFoundException::class)]
|
||||
class NotFoundExceptionTest extends TestCase
|
||||
{
|
||||
public function testConstructor(): void
|
||||
|
||||
@@ -21,13 +21,13 @@ use App\Repository\ProjectRateRepository;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\VisibilityInterface;
|
||||
use App\Tests\Mocks\ProjectTestMetaFieldSubscriberMock;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class ProjectControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
use RateControllerTestTrait;
|
||||
@@ -191,9 +191,7 @@ class ProjectControllerTest extends APIControllerBaseTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getCollectionTestData
|
||||
*/
|
||||
#[DataProvider('getCollectionTestData')]
|
||||
public function testGetCollectionWithParams(string $url, ?int $project, array $parameters, array $expected): void
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||
|
||||
@@ -15,6 +15,7 @@ use FOS\RestBundle\Serializer\Normalizer\FlattenExceptionHandler;
|
||||
use JMS\Serializer\GraphNavigatorInterface;
|
||||
use JMS\Serializer\JsonSerializationVisitor;
|
||||
use JMS\Serializer\SerializationContext;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||
@@ -22,9 +23,7 @@ use Symfony\Component\Validator\ConstraintViolation;
|
||||
use Symfony\Component\Validator\ConstraintViolationList;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\API\Serializer\ValidationFailedExceptionErrorHandler
|
||||
*/
|
||||
#[CoversClass(ValidationFailedExceptionErrorHandler::class)]
|
||||
class ValidationFailedExceptionErrorHandlerTest extends TestCase
|
||||
{
|
||||
public function testSubscribingMethods(): void
|
||||
|
||||
@@ -11,10 +11,9 @@ namespace App\Tests\API;
|
||||
|
||||
use App\Constants;
|
||||
use App\Entity\User;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class StatusControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
public function testIsSecurePing(): void
|
||||
|
||||
@@ -12,11 +12,10 @@ namespace App\Tests\API;
|
||||
use App\Entity\Tag;
|
||||
use App\Entity\User;
|
||||
use App\Tests\DataFixtures\TagFixtures;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class TagControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -13,11 +13,11 @@ use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
use App\Tests\DataFixtures\TeamFixtures;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class TeamControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
/**
|
||||
@@ -47,9 +47,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getRoleTestData
|
||||
*/
|
||||
#[DataProvider('getRoleTestData')]
|
||||
public function testIsSecureForRole(string $role): void
|
||||
{
|
||||
$this->assertUrlIsSecuredForRole($role, '/api/teams');
|
||||
|
||||
@@ -20,12 +20,12 @@ use App\Entity\User;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
use App\Tests\Mocks\TimesheetTestMetaFieldSubscriberMock;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class TimesheetControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
public const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
@@ -779,9 +779,7 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTrackingModeTestData
|
||||
*/
|
||||
#[DataProvider('getTrackingModeTestData')]
|
||||
public function testCreateActionWithTrackingModeHasFieldsForUser(string $trackingMode, string $user, bool $showTimes): void
|
||||
{
|
||||
$dateTime = new DateTimeFactory(new \DateTimeZone(self::TEST_TIMEZONE));
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
namespace App\Tests\API;
|
||||
|
||||
use App\Entity\User;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
/**
|
||||
* @group integration
|
||||
*/
|
||||
#[Group('integration')]
|
||||
class UserControllerTest extends APIControllerBaseTestCase
|
||||
{
|
||||
public function testIsSecure(): void
|
||||
@@ -33,9 +33,7 @@ class UserControllerTest extends APIControllerBaseTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getRoleTestData
|
||||
*/
|
||||
#[DataProvider('getRoleTestData')]
|
||||
public function testIsSecureForRole(string $role): void
|
||||
{
|
||||
$this->assertUrlIsSecuredForRole($role, '/api/users');
|
||||
|
||||
@@ -21,15 +21,15 @@ use App\Event\ActivityUpdatePreEvent;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Tests\Mocks\SystemConfigurationFactory;
|
||||
use App\Validator\ValidationFailedException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
use Symfony\Component\Validator\ConstraintViolationList;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\Activity\ActivityService
|
||||
*/
|
||||
#[CoversClass(ActivityService::class)]
|
||||
class ActivityServiceTest extends TestCase
|
||||
{
|
||||
private function getSut(
|
||||
@@ -144,9 +144,7 @@ class ActivityServiceTest extends TestCase
|
||||
self::assertNull($project->getProject());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTestData
|
||||
*/
|
||||
#[DataProvider('getTestData')]
|
||||
public function testActivityNumber(string $format, int|string $expected): void
|
||||
{
|
||||
$sut = $this->getSut(null, null, null, ['number_format' => $format]);
|
||||
|
||||
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'];
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,10 @@ namespace App\Tests\Calendar;
|
||||
|
||||
use App\Calendar\CalendarSource;
|
||||
use App\Calendar\CalendarSourceType;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Calendar\CalendarSource
|
||||
*/
|
||||
#[CoversClass(CalendarSource::class)]
|
||||
class CalendarSourceTest extends TestCase
|
||||
{
|
||||
public function testSource(): void
|
||||
|
||||
@@ -10,11 +10,10 @@
|
||||
namespace App\Tests\Calendar;
|
||||
|
||||
use App\Calendar\GoogleSource;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Calendar\GoogleSource
|
||||
*/
|
||||
#[CoversClass(GoogleSource::class)]
|
||||
class GoogleSourceTest extends TestCase
|
||||
{
|
||||
public function testConstruct(): void
|
||||
|
||||
@@ -11,11 +11,10 @@ namespace App\Tests\Calendar;
|
||||
|
||||
use App\Calendar\Google;
|
||||
use App\Calendar\GoogleSource;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Calendar\Google
|
||||
*/
|
||||
#[CoversClass(Google::class)]
|
||||
class GoogleTest extends TestCase
|
||||
{
|
||||
public function testConstruct(): void
|
||||
|
||||
@@ -12,11 +12,10 @@ namespace App\Tests\Calendar;
|
||||
use App\Calendar\RecentActivitiesSource;
|
||||
use App\Calendar\TimesheetEntry;
|
||||
use App\Entity\Timesheet;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Calendar\RecentActivitiesSource
|
||||
*/
|
||||
#[CoversClass(RecentActivitiesSource::class)]
|
||||
class RecentActivitiesSourceTest extends TestCase
|
||||
{
|
||||
public function testConstruct(): void
|
||||
|
||||
@@ -14,11 +14,10 @@ use App\Entity\Activity;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Tag;
|
||||
use App\Entity\Timesheet;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Calendar\TimesheetEntry
|
||||
*/
|
||||
#[CoversClass(TimesheetEntry::class)]
|
||||
class TimesheetEntryTest extends TestCase
|
||||
{
|
||||
public function testConstruct(): void
|
||||
|
||||
@@ -14,15 +14,15 @@ use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\ActivateUserCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(ActivateUserCommand::class)]
|
||||
#[Group('integration')]
|
||||
class ActivateUserCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\AbstractBundleInstallerCommand;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
@@ -18,10 +20,8 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\AbstractBundleInstallerCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(AbstractBundleInstallerCommand::class)]
|
||||
#[Group('integration')]
|
||||
class BundleInstallerCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -9,22 +9,23 @@
|
||||
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\AbstractUserCommand;
|
||||
use App\Command\ChangePasswordCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\ChangePasswordCommand
|
||||
* @covers \App\Command\AbstractUserCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(ChangePasswordCommand::class)]
|
||||
#[CoversClass(AbstractUserCommand::class)]
|
||||
#[Group('integration')]
|
||||
class ChangePasswordCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -14,14 +14,14 @@ use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\CreateUserCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(CreateUserCommand::class)]
|
||||
#[Group('integration')]
|
||||
class CreateUserCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -14,15 +14,15 @@ use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\DeactivateUserCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(DeactivateUserCommand::class)]
|
||||
#[Group('integration')]
|
||||
class DeactivateUserCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -9,21 +9,22 @@
|
||||
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\AbstractRoleCommand;
|
||||
use App\Command\DemoteUserCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\AbstractRoleCommand
|
||||
* @covers \App\Command\DemoteUserCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(AbstractRoleCommand::class)]
|
||||
#[CoversClass(DemoteUserCommand::class)]
|
||||
#[Group('integration')]
|
||||
class DemoteUserCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -23,16 +23,16 @@ use App\Tests\DataFixtures\CustomerFixtures;
|
||||
use App\Tests\DataFixtures\ProjectFixtures;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
use App\Tests\KernelTestTrait;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\ExportCreateCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(ExportCreateCommand::class)]
|
||||
#[Group('integration')]
|
||||
class ExportCreateCommandTest extends KernelTestCase
|
||||
{
|
||||
use KernelTestTrait;
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace App\Tests\Command;
|
||||
|
||||
use App\Command\InstallCommand;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\InstallCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(InstallCommand::class)]
|
||||
#[Group('integration')]
|
||||
class InstallCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -23,14 +23,14 @@ use App\Tests\DataFixtures\InvoiceTemplateFixtures;
|
||||
use App\Tests\DataFixtures\ProjectFixtures;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
use App\Tests\KernelTestTrait;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\InvoiceCreateCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(InvoiceCreateCommand::class)]
|
||||
#[Group('integration')]
|
||||
class InvoiceCreateCommandTest extends KernelTestCase
|
||||
{
|
||||
use KernelTestTrait;
|
||||
@@ -193,7 +193,7 @@ class InvoiceCreateCommandTest extends KernelTestCase
|
||||
|
||||
$fixture = new CustomerFixtures();
|
||||
$fixture->setAmount(1);
|
||||
$fixture->setCallback(function (Customer $customer) use ($invoiceTemplate) {
|
||||
$fixture->setCallback(function (Customer $customer) use ($invoiceTemplate): void {
|
||||
$customer->setInvoiceTemplate($invoiceTemplate[0]);
|
||||
});
|
||||
$customer = $this->importFixture($fixture)[0];
|
||||
|
||||
@@ -11,14 +11,14 @@ namespace App\Tests\Command;
|
||||
|
||||
use App\Command\ListUserCommand;
|
||||
use App\Repository\UserRepository;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\ListUserCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(ListUserCommand::class)]
|
||||
#[Group('integration')]
|
||||
class ListUserCommandTest extends KernelTestCase
|
||||
{
|
||||
public function testWithPlugins(): void
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\MailTestCommand;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\MailTestCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(MailTestCommand::class)]
|
||||
#[Group('integration')]
|
||||
class MailTestCommandTest extends KernelTestCase
|
||||
{
|
||||
protected Application $application;
|
||||
|
||||
@@ -13,14 +13,14 @@ use App\Command\PluginCommand;
|
||||
use App\Plugin\PackageManager;
|
||||
use App\Plugin\PluginInterface;
|
||||
use App\Plugin\PluginManager;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\PluginCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(PluginCommand::class)]
|
||||
#[Group('integration')]
|
||||
class PluginCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -9,21 +9,22 @@
|
||||
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\AbstractRoleCommand;
|
||||
use App\Command\PromoteUserCommand;
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\User\UserService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\AbstractRoleCommand
|
||||
* @covers \App\Command\PromoteUserCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(AbstractRoleCommand::class)]
|
||||
#[CoversClass(PromoteUserCommand::class)]
|
||||
#[Group('integration')]
|
||||
class PromoteUserCommandTest extends KernelTestCase
|
||||
{
|
||||
private Application $application;
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\ReloadCommand;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\ReloadCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(ReloadCommand::class)]
|
||||
#[Group('integration')]
|
||||
class ReloadCommandTest extends KernelTestCase
|
||||
{
|
||||
protected Application $application;
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\ResetDevelopmentCommand;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @covers \App\Command\ResetDevelopmentCommand
|
||||
* @group integration
|
||||
*/
|
||||
#[CoversClass(ResetDevelopmentCommand::class)]
|
||||
#[Group('integration')]
|
||||
class ResetDevelopmentCommandTest extends KernelTestCase
|
||||
{
|
||||
public function testCommandName(): void
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user