Release 2.19 (#4922)
This commit is contained in:
2
.github/workflows/testing.yaml
vendored
2
.github/workflows/testing.yaml
vendored
@@ -56,7 +56,7 @@ jobs:
|
|||||||
run: composer install
|
run: composer install
|
||||||
|
|
||||||
- name: Validate Composer
|
- name: Validate Composer
|
||||||
run: composer validate --strict
|
run: composer validate --strict --no-check-all
|
||||||
|
|
||||||
- name: Warmup cache
|
- name: Warmup cache
|
||||||
run: APP_ENV=dev bin/console kimai:reload -n
|
run: APP_ENV=dev bin/console kimai:reload -n
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
|
|
||||||
@include color-mode(dark, true) {
|
@include color-mode(dark, true) {
|
||||||
|
$dark-font-opacity: 0.6;
|
||||||
|
|
||||||
|
/*
|
||||||
|
--tblr-body-bg: #1e222b;
|
||||||
|
--tblr-bg-surface-tertiary: #1e222b;
|
||||||
|
--tblr-bg-surface: #1e222b;
|
||||||
|
--tblr-bg-surface-secondary: #17202b;
|
||||||
|
//--tblr-muted: #d9dcdf;
|
||||||
|
*/
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
color: inherit;
|
||||||
|
opacity: $dark-font-opacity;
|
||||||
|
}
|
||||||
table.dataTable {
|
table.dataTable {
|
||||||
th.today {
|
th.today {
|
||||||
color: var(--tblr-muted);
|
color: var(--tblr-muted);
|
||||||
}
|
}
|
||||||
|
thead .sorting:after {
|
||||||
|
opacity: $dark-font-opacity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
@@ -12,4 +30,16 @@
|
|||||||
color: var(--tblr-white);
|
color: var(--tblr-white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Fix tabs pills (e.g. user profile) */
|
||||||
|
.nav {
|
||||||
|
--tblr-nav-link-color: inherit;
|
||||||
|
opacity: $dark-font-opacity;
|
||||||
|
}
|
||||||
|
.datagrid-title {
|
||||||
|
color: inherit;
|
||||||
|
opacity: $dark-font-opacity;
|
||||||
|
}
|
||||||
|
.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
"symfony/security-bundle": "^6.0",
|
"symfony/security-bundle": "^6.0",
|
||||||
"symfony/security-csrf": "^6.0",
|
"symfony/security-csrf": "^6.0",
|
||||||
"symfony/serializer": "^6.0",
|
"symfony/serializer": "^6.0",
|
||||||
|
"symfony/string": "v6.4.8",
|
||||||
"symfony/translation": "^6.0",
|
"symfony/translation": "^6.0",
|
||||||
"symfony/twig-bundle": "^6.0",
|
"symfony/twig-bundle": "^6.0",
|
||||||
"symfony/validator": "^6.0",
|
"symfony/validator": "^6.0",
|
||||||
@@ -171,7 +172,6 @@
|
|||||||
"@tests-integration"
|
"@tests-integration"
|
||||||
],
|
],
|
||||||
"linting": [
|
"linting": [
|
||||||
"composer validate --strict",
|
|
||||||
"bin/console lint:container",
|
"bin/console lint:container",
|
||||||
"bin/console lint:yaml config --parse-tags",
|
"bin/console lint:yaml config --parse-tags",
|
||||||
"bin/console lint:twig templates --show-deprecations",
|
"bin/console lint:twig templates --show-deprecations",
|
||||||
|
|||||||
555
composer.lock
generated
555
composer.lock
generated
File diff suppressed because it is too large
Load Diff
15
phpstan.neon
15
phpstan.neon
@@ -4008,21 +4008,6 @@ parameters:
|
|||||||
count: 2
|
count: 2
|
||||||
path: src/Project/ProjectStatisticService.php
|
path: src/Project/ProjectStatisticService.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$counter of method App\\\\Model\\\\TimesheetCountedStatistic\\:\\:setCounter\\(\\) expects int, float\\|int given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Project/ProjectStatisticService.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$counter of method App\\\\Model\\\\TimesheetCountedStatistic\\:\\:setCounterBillable\\(\\) expects int, float\\|int given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Project/ProjectStatisticService.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$counter of method App\\\\Model\\\\TimesheetCountedStatistic\\:\\:setCounterExported\\(\\) expects int, float\\|int given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Project/ProjectStatisticService.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$datetime of method App\\\\Timesheet\\\\DateTimeFactory\\:\\:createDateTime\\(\\) expects string, mixed given\\.$#"
|
message: "#^Parameter \\#1 \\$datetime of method App\\\\Timesheet\\\\DateTimeFactory\\:\\:createDateTime\\(\\) expects string, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|||||||
@@ -36,9 +36,14 @@ DirectoryIndex index.php
|
|||||||
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
|
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
|
||||||
RewriteRule .* - [E=BASE:%1]
|
RewriteRule .* - [E=BASE:%1]
|
||||||
|
|
||||||
# Sets the HTTP_AUTHORIZATION header removed by Apache
|
# Sets the HTTP_AUTHORIZATION header removed by Apache, if PHP is installed as CGI
|
||||||
|
# Required for the API and below are 3 different methods to achieve it
|
||||||
RewriteCond %{HTTP:Authorization} .+
|
RewriteCond %{HTTP:Authorization} .+
|
||||||
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
|
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
|
||||||
|
# This might be the only required line
|
||||||
|
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||||
|
# Apache 2.4.13+
|
||||||
|
# CGIPassAuth On
|
||||||
|
|
||||||
# Redirect to URI without front controller to prevent duplicate content
|
# Redirect to URI without front controller to prevent duplicate content
|
||||||
# (with and without `/index.php`). Only do this redirect on the initial
|
# (with and without `/index.php`). Only do this redirect on the initial
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
|||||||
"/build/app.41121fe4.js"
|
"/build/app.41121fe4.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"/build/app.ee4f7e19.css"
|
"/build/app.a7ba9fd6.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"app-rtl": {
|
"app-rtl": {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"/build/app-rtl.97153087.js"
|
"/build/app-rtl.97153087.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"/build/app-rtl.db588f2b.css"
|
"/build/app-rtl.6516df2d.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"export-pdf": {
|
"export-pdf": {
|
||||||
@@ -73,9 +73,9 @@
|
|||||||
"integrity": {
|
"integrity": {
|
||||||
"/build/runtime.74179306.js": "sha384-OC1hTNUXUalKJcvmzrZ0TMCOIwnhxCxgG9dQkbcwR7WcBBCkl2H8bs3giiT2pAwG",
|
"/build/runtime.74179306.js": "sha384-OC1hTNUXUalKJcvmzrZ0TMCOIwnhxCxgG9dQkbcwR7WcBBCkl2H8bs3giiT2pAwG",
|
||||||
"/build/app.41121fe4.js": "sha384-4v3lVToJIcpMJ++4WaIZ5r9rXR5Vb4waSoHcEzx3vOfcLL+1+TN70di7XPzYSW7I",
|
"/build/app.41121fe4.js": "sha384-4v3lVToJIcpMJ++4WaIZ5r9rXR5Vb4waSoHcEzx3vOfcLL+1+TN70di7XPzYSW7I",
|
||||||
"/build/app.ee4f7e19.css": "sha384-7E3ujVsOCkGatHu6khaWWqYhPkc85k22zuXZOwOVo1MlWcPSEBhS65Y0qbeWXdAp",
|
"/build/app.a7ba9fd6.css": "sha384-GfMYf1vYI9zkkywMoxYk6wpC4UyK185zjDBi16qa0237oztrtOuWdBZsKjWOkyeY",
|
||||||
"/build/app-rtl.97153087.js": "sha384-jX7jRUAa8rH29Eg8jLIUKGfGcOT6RBz/P90plXmZPadf2CXKUBdcNGrspaejCHkr",
|
"/build/app-rtl.97153087.js": "sha384-jX7jRUAa8rH29Eg8jLIUKGfGcOT6RBz/P90plXmZPadf2CXKUBdcNGrspaejCHkr",
|
||||||
"/build/app-rtl.db588f2b.css": "sha384-OwYS82WpkkoVqI38n3gGpvMTiTFT+dUscgVglMw2vcDvIxAe2B3IcXMjHoCGm/gl",
|
"/build/app-rtl.6516df2d.css": "sha384-5hF5akg98Hnj/bihpbsiTjMyZ3BsAC4+Z1kzqi1O3/U+tgpnQ+JmwsOADDpMld6d",
|
||||||
"/build/export-pdf.1442bee7.js": "sha384-C6agvjJnQUsMCxaZ/J7dUZU3cpC7uHKI9ZtnfGpRqYbjyw91YLy1oV3iNYgwYyCF",
|
"/build/export-pdf.1442bee7.js": "sha384-C6agvjJnQUsMCxaZ/J7dUZU3cpC7uHKI9ZtnfGpRqYbjyw91YLy1oV3iNYgwYyCF",
|
||||||
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
|
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
|
||||||
"/build/invoice.7ef8a0c8.js": "sha384-z4lZ1Ig3+NPigrRyGPZoff0gG3n5PnCjaDJ73ATnzdUYk0lOCEtNV9fg35VRD0vG",
|
"/build/invoice.7ef8a0c8.js": "sha384-z4lZ1Ig3+NPigrRyGPZoff0gG3n5PnCjaDJ73ATnzdUYk0lOCEtNV9fg35VRD0vG",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build/app.css": "/build/app.ee4f7e19.css",
|
"build/app.css": "/build/app.a7ba9fd6.css",
|
||||||
"build/app.js": "/build/app.41121fe4.js",
|
"build/app.js": "/build/app.41121fe4.js",
|
||||||
"build/app-rtl.css": "/build/app-rtl.db588f2b.css",
|
"build/app-rtl.css": "/build/app-rtl.6516df2d.css",
|
||||||
"build/app-rtl.js": "/build/app-rtl.97153087.js",
|
"build/app-rtl.js": "/build/app-rtl.97153087.js",
|
||||||
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
|
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
|
||||||
"build/export-pdf.js": "/build/export-pdf.1442bee7.js",
|
"build/export-pdf.js": "/build/export-pdf.1442bee7.js",
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ final class SystemConfiguration
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal will be made private soon after 2.18.0 - do ot access this method directly, but through getThemeColors()
|
* @internal will be made private soon after 2.18.0 - do not access this method directly, but through getThemeColors()
|
||||||
*/
|
*/
|
||||||
public function getThemeColorChoices(): string
|
public function getThemeColorChoices(): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ class Constants
|
|||||||
/**
|
/**
|
||||||
* The current release version
|
* The current release version
|
||||||
*/
|
*/
|
||||||
public const VERSION = '2.18.0';
|
public const VERSION = '2.19.0';
|
||||||
/**
|
/**
|
||||||
* The current release: major * 10000 + minor * 100 + patch
|
* The current release: major * 10000 + minor * 100 + patch
|
||||||
*/
|
*/
|
||||||
public const VERSION_ID = 21800;
|
public const VERSION_ID = 21900;
|
||||||
/**
|
/**
|
||||||
* The software name
|
* The software name
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ final class ContractController extends AbstractController
|
|||||||
'boxes' => $controllerEvent->getController(),
|
'boxes' => $controllerEvent->getController(),
|
||||||
'year' => $year,
|
'year' => $year,
|
||||||
'user' => $profile,
|
'user' => $profile,
|
||||||
'form' => $form->createView(),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use App\Configuration\SystemConfiguration;
|
use App\Configuration\SystemConfiguration;
|
||||||
use App\Form\QuickEntryForm;
|
use App\Form\QuickEntryForm;
|
||||||
|
use App\Form\WeekByUserForm;
|
||||||
use App\Model\QuickEntryWeek;
|
use App\Model\QuickEntryWeek;
|
||||||
|
use App\Reporting\WeekByUser\WeekByUser;
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Repository\TimesheetRepository;
|
use App\Repository\TimesheetRepository;
|
||||||
use App\Timesheet\FavoriteRecordService;
|
use App\Timesheet\FavoriteRecordService;
|
||||||
@@ -25,7 +27,6 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|||||||
/**
|
/**
|
||||||
* Controller used to enter times in weekly form.
|
* Controller used to enter times in weekly form.
|
||||||
*/
|
*/
|
||||||
#[Route(path: '/quick_entry')]
|
|
||||||
#[IsGranted('quick-entry')]
|
#[IsGranted('quick-entry')]
|
||||||
final class QuickEntryController extends AbstractController
|
final class QuickEntryController extends AbstractController
|
||||||
{
|
{
|
||||||
@@ -38,18 +39,30 @@ final class QuickEntryController extends AbstractController
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route(path: '/{begin}', name: 'quick_entry', methods: ['GET', 'POST'])]
|
#[Route(path: '/quick_entry/', name: 'quick_entry', methods: ['GET', 'POST'])]
|
||||||
public function quickEntry(Request $request, ?string $begin = null): Response
|
public function quickEntry(Request $request): Response
|
||||||
{
|
{
|
||||||
$factory = $this->getDateTimeFactory();
|
$user = $this->getUser();
|
||||||
|
$factory = $this->getDateTimeFactory($user);
|
||||||
|
$defaultDate = $factory->createDateTime();
|
||||||
|
|
||||||
if ($begin !== null) {
|
$values = new WeekByUser();
|
||||||
try {
|
$values->setUser($user);
|
||||||
$begin = $factory->createDateTime($begin);
|
$values->setDate($defaultDate);
|
||||||
} catch (\Exception $ex) {
|
|
||||||
$begin = null;
|
$weeklyForm = $this->createFormForGetRequest(WeekByUserForm::class, $values, [
|
||||||
}
|
'include_user' => $this->isGranted('view_other_timesheet'),
|
||||||
}
|
'timezone' => $factory->getTimezone()->getName(),
|
||||||
|
'start_date' => $values->getDate(),
|
||||||
|
'attr' => ['name' => 'quick_entry_weekrange_form']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$weeklyForm->submit($request->query->all(), false);
|
||||||
|
|
||||||
|
$user = $values->getUser() ?? $user;
|
||||||
|
$factory = $this->getDateTimeFactory($user);
|
||||||
|
|
||||||
|
$begin = $values->getDate();
|
||||||
|
|
||||||
if ($begin === null) {
|
if ($begin === null) {
|
||||||
$begin = $factory->createDateTime();
|
$begin = $factory->createDateTime();
|
||||||
@@ -57,7 +70,6 @@ final class QuickEntryController extends AbstractController
|
|||||||
|
|
||||||
$startWeek = $factory->getStartOfWeek($begin);
|
$startWeek = $factory->getStartOfWeek($begin);
|
||||||
$endWeek = $factory->getEndOfWeek($begin);
|
$endWeek = $factory->getEndOfWeek($begin);
|
||||||
$user = $this->getUser();
|
|
||||||
|
|
||||||
$tmpDay = clone $startWeek;
|
$tmpDay = clone $startWeek;
|
||||||
$week = [];
|
$week = [];
|
||||||
@@ -235,7 +247,7 @@ final class QuickEntryController extends AbstractController
|
|||||||
if ($saved) {
|
if ($saved) {
|
||||||
$this->flashSuccess('action.update.success');
|
$this->flashSuccess('action.update.success');
|
||||||
|
|
||||||
return $this->redirectToRoute('quick_entry', ['begin' => $begin->format('Y-m-d')]);
|
return $this->redirectToRoute('quick_entry', ['date' => $begin->format('Y-m-d'), 'user' => $user->getId()]);
|
||||||
}
|
}
|
||||||
} catch (\Exception $ex) {
|
} catch (\Exception $ex) {
|
||||||
$this->flashUpdateException($ex);
|
$this->flashUpdateException($ex);
|
||||||
@@ -244,6 +256,8 @@ final class QuickEntryController extends AbstractController
|
|||||||
|
|
||||||
$page = new PageSetup('quick_entry.title');
|
$page = new PageSetup('quick_entry.title');
|
||||||
$page->setHelp('weekly-times.html');
|
$page->setHelp('weekly-times.html');
|
||||||
|
$page->setPaginationForm($weeklyForm);
|
||||||
|
$page->setActionName('weekly-times');
|
||||||
|
|
||||||
return $this->render('quick-entry/index.html.twig', [
|
return $this->render('quick-entry/index.html.twig', [
|
||||||
'page_setup' => $page,
|
'page_setup' => $page,
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ final class ReportUsersYearController extends AbstractController
|
|||||||
$dateTimeFactory = $this->getDateTimeFactory();
|
$dateTimeFactory = $this->getDateTimeFactory();
|
||||||
|
|
||||||
$defaultDate = $dateTimeFactory->createStartOfYear();
|
$defaultDate = $dateTimeFactory->createStartOfYear();
|
||||||
|
$isFinancialYear = false;
|
||||||
|
|
||||||
if (null !== ($financialYear = $systemConfiguration->getFinancialYearStart())) {
|
if (null !== ($financialYear = $systemConfiguration->getFinancialYearStart())) {
|
||||||
$defaultDate = $this->getDateTimeFactory()->createStartOfFinancialYear($financialYear);
|
$defaultDate = $this->getDateTimeFactory()->createStartOfFinancialYear($financialYear);
|
||||||
|
$isFinancialYear = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$values = new YearlyUserList();
|
$values = new YearlyUserList();
|
||||||
@@ -72,6 +74,7 @@ final class ReportUsersYearController extends AbstractController
|
|||||||
$form = $this->createFormForGetRequest(YearlyUserListForm::class, $values, [
|
$form = $this->createFormForGetRequest(YearlyUserListForm::class, $values, [
|
||||||
'timezone' => $dateTimeFactory->getTimezone()->getName(),
|
'timezone' => $dateTimeFactory->getTimezone()->getName(),
|
||||||
'start_date' => $values->getDate(),
|
'start_date' => $values->getDate(),
|
||||||
|
'show_range' => $isFinancialYear,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$form->submit($request->query->all(), false);
|
$form->submit($request->query->all(), false);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ namespace App\Form;
|
|||||||
|
|
||||||
use App\Configuration\SystemConfiguration;
|
use App\Configuration\SystemConfiguration;
|
||||||
use App\Form\Type\QuickEntryWeekType;
|
use App\Form\Type\QuickEntryWeekType;
|
||||||
use App\Form\Type\WeekPickerType;
|
|
||||||
use App\Model\QuickEntryWeek;
|
use App\Model\QuickEntryWeek;
|
||||||
use App\Validator\Constraints\QuickEntryModel;
|
use App\Validator\Constraints\QuickEntryModel;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
@@ -57,13 +56,6 @@ final class QuickEntryForm extends AbstractType
|
|||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
$builder->add('date', WeekPickerType::class, [
|
|
||||||
'model_timezone' => $options['timezone'],
|
|
||||||
'view_timezone' => $options['timezone'],
|
|
||||||
'start_date' => $options['start_date'],
|
|
||||||
'label' => false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$builder->add('rows', CollectionType::class, [
|
$builder->add('rows', CollectionType::class, [
|
||||||
'label' => false,
|
'label' => false,
|
||||||
'entry_type' => QuickEntryWeekType::class,
|
'entry_type' => QuickEntryWeekType::class,
|
||||||
|
|||||||
48
src/Form/WeekByUserForm.php
Normal file
48
src/Form/WeekByUserForm.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use App\Form\Type\UserType;
|
||||||
|
use App\Form\Type\WeekPickerType;
|
||||||
|
use App\Reporting\WeekByUser\WeekByUser;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
final class WeekByUserForm extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder->add('date', WeekPickerType::class, [
|
||||||
|
'model_timezone' => $options['timezone'],
|
||||||
|
'view_timezone' => $options['timezone'],
|
||||||
|
'start_date' => $options['start_date'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($options['include_user']) {
|
||||||
|
$builder->add('user', UserType::class, [
|
||||||
|
'width' => false,
|
||||||
|
'include_current_user_if_system_account' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => WeekByUser::class,
|
||||||
|
'timezone' => date_default_timezone_get(),
|
||||||
|
'start_date' => new \DateTime(),
|
||||||
|
'include_user' => false,
|
||||||
|
'csrf_protection' => false,
|
||||||
|
'method' => 'GET',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ class QuickEntryModel
|
|||||||
*/
|
*/
|
||||||
private array $timesheets = [];
|
private array $timesheets = [];
|
||||||
|
|
||||||
public function __construct(private ?User $user = null, private ?Project $project = null, private ?Activity $activity = null)
|
public function __construct(private User $user, private ?Project $project = null, private ?Activity $activity = null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class QuickEntryModel
|
|||||||
return $this->prototype;
|
return $this->prototype;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUser(): ?User
|
public function getUser(): User
|
||||||
{
|
{
|
||||||
return $this->user;
|
return $this->user;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class QuickEntryWeek
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addRow(?User $user = null, ?Project $project = null, ?Activity $activity = null): QuickEntryModel
|
public function addRow(User $user, ?Project $project = null, ?Activity $activity = null): QuickEntryModel
|
||||||
{
|
{
|
||||||
$model = $this->createRow($user, $project, $activity);
|
$model = $this->createRow($user, $project, $activity);
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class QuickEntryWeek
|
|||||||
return $model;
|
return $model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createRow(?User $user = null, ?Project $project = null, ?Activity $activity = null): QuickEntryModel
|
public function createRow(User $user, ?Project $project = null, ?Activity $activity = null): QuickEntryModel
|
||||||
{
|
{
|
||||||
return new QuickEntryModel($user, $project, $activity);
|
return new QuickEntryModel($user, $project, $activity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ final class YearlyUserListForm extends AbstractType
|
|||||||
'model_timezone' => $options['timezone'],
|
'model_timezone' => $options['timezone'],
|
||||||
'view_timezone' => $options['timezone'],
|
'view_timezone' => $options['timezone'],
|
||||||
'start_date' => $options['start_date'],
|
'start_date' => $options['start_date'],
|
||||||
'show_range' => true,
|
'show_range' => $options['show_range'],
|
||||||
]);
|
]);
|
||||||
$builder->add('team', TeamType::class, [
|
$builder->add('team', TeamType::class, [
|
||||||
'multiple' => false,
|
'multiple' => false,
|
||||||
@@ -51,6 +51,7 @@ final class YearlyUserListForm extends AbstractType
|
|||||||
'start_date' => new \DateTime(),
|
'start_date' => new \DateTime(),
|
||||||
'csrf_protection' => false,
|
'csrf_protection' => false,
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
|
'show_range' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ final class Extensions extends AbstractExtension
|
|||||||
public function getFunctions(): array
|
public function getFunctions(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
new TwigFunction('report_date', [$this, 'buildReportDate']),
|
||||||
new TwigFunction('class_name', [$this, 'getClassName']),
|
new TwigFunction('class_name', [$this, 'getClassName']),
|
||||||
new TwigFunction('iso_day_by_name', [$this, 'getIsoDayByName']),
|
new TwigFunction('iso_day_by_name', [$this, 'getIsoDayByName']),
|
||||||
new TwigFunction('random_color', [$this, 'randomColor']),
|
new TwigFunction('random_color', [$this, 'randomColor']),
|
||||||
@@ -52,6 +53,38 @@ final class Extensions extends AbstractExtension
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function buildReportDate(string|int $year, string|int $month = 1, string|int $day = 1): \DateTimeImmutable
|
||||||
|
{
|
||||||
|
if (\is_string($month)) {
|
||||||
|
$month = (int) $month;
|
||||||
|
}
|
||||||
|
if ($month > 12 || $month < 1) {
|
||||||
|
throw new \InvalidArgumentException('Unknown month: ' . $month);
|
||||||
|
}
|
||||||
|
if ($month < 10) {
|
||||||
|
$month = '0' . $month;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_string($day)) {
|
||||||
|
$day = (int) $day;
|
||||||
|
}
|
||||||
|
if ($day > 31 || $day < 1) {
|
||||||
|
throw new \InvalidArgumentException('Unknown day: ' . $day);
|
||||||
|
}
|
||||||
|
if ($day < 10) {
|
||||||
|
$day = '0' . $day;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_string($year)) {
|
||||||
|
$year = (int) $year;
|
||||||
|
}
|
||||||
|
if ($year < 1980 || $year > 2100) {
|
||||||
|
throw new \InvalidArgumentException('Unknown year: ' . $year);
|
||||||
|
}
|
||||||
|
|
||||||
|
return \DateTimeImmutable::createFromFormat('Y-m-d', $year . '-' . $month . '-' . $day); // @phpstan-ignore-line
|
||||||
|
}
|
||||||
|
|
||||||
public function formatReportDate(\DateTimeInterface $dateTime): string
|
public function formatReportDate(\DateTimeInterface $dateTime): string
|
||||||
{
|
{
|
||||||
return $dateTime->format(self::REPORT_DATE);
|
return $dateTime->format(self::REPORT_DATE);
|
||||||
|
|||||||
@@ -189,18 +189,36 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
|
|||||||
return $this->getFormatter()->dayName($dateTime, $short);
|
return $this->getFormatter()->dayName($dateTime, $short);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getJavascriptConfiguration(User $user): array
|
public function getJavascriptConfiguration(?User $user = null): array
|
||||||
{
|
{
|
||||||
|
$language = User::DEFAULT_LANGUAGE;
|
||||||
|
$browserTitle = false;
|
||||||
|
$id = null;
|
||||||
|
$name = 'anonymous';
|
||||||
|
$admin = false;
|
||||||
|
$superAdmin = false;
|
||||||
|
$timezone = date_default_timezone_get();
|
||||||
|
|
||||||
|
if ($user !== null) {
|
||||||
|
$browserTitle = (bool) $user->getPreferenceValue('update_browser_title');
|
||||||
|
$language = $user->getLanguage();
|
||||||
|
$id = $user->getId();
|
||||||
|
$name = $user->getDisplayName();
|
||||||
|
$admin = $user->isAdmin();
|
||||||
|
$superAdmin = $user->isSuperAdmin();
|
||||||
|
$timezone = $user->getTimezone();
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'locale' => $this->locale,
|
'locale' => $this->locale,
|
||||||
'language' => $user->getLanguage(),
|
'language' => $language,
|
||||||
'formatDuration' => $this->localeService->getDurationFormat($this->locale),
|
'formatDuration' => $this->localeService->getDurationFormat($this->locale),
|
||||||
'formatDate' => $this->localeService->getDateFormat($this->locale),
|
'formatDate' => $this->localeService->getDateFormat($this->locale),
|
||||||
'defaultColor' => Constants::DEFAULT_COLOR,
|
'defaultColor' => Constants::DEFAULT_COLOR,
|
||||||
'twentyFourHours' => $this->localeService->is24Hour($this->locale),
|
'twentyFourHours' => $this->localeService->is24Hour($this->locale),
|
||||||
'updateBrowserTitle' => (bool) $user->getPreferenceValue('update_browser_title'),
|
'updateBrowserTitle' => $browserTitle,
|
||||||
'timezone' => $user->getTimezone(),
|
'timezone' => $timezone,
|
||||||
'user' => ['id' => $user->getId(), 'name' => $user->getDisplayName(), 'admin' => $user->isAdmin(), 'superAdmin' => $user->isSuperAdmin()],
|
'user' => ['id' => $id, 'name' => $name, 'admin' => $admin, 'superAdmin' => $superAdmin],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,14 @@ use Psr\EventDispatcher\EventDispatcherInterface;
|
|||||||
*/
|
*/
|
||||||
final class WorkingTimeService
|
final class WorkingTimeService
|
||||||
{
|
{
|
||||||
public function __construct(private TimesheetRepository $timesheetRepository, private WorkingTimeRepository $workingTimeRepository, private EventDispatcherInterface $eventDispatcher)
|
/** @var array<string, WorkingTime|null> */
|
||||||
|
private array $latestApprovals = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly TimesheetRepository $timesheetRepository,
|
||||||
|
private readonly WorkingTimeRepository $workingTimeRepository,
|
||||||
|
private readonly EventDispatcherInterface $eventDispatcher
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +50,36 @@ final class WorkingTimeService
|
|||||||
|
|
||||||
public function getLatestApproval(User $user): ?WorkingTime
|
public function getLatestApproval(User $user): ?WorkingTime
|
||||||
{
|
{
|
||||||
return $this->workingTimeRepository->getLatestApproval($user);
|
if ($user->getId() === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = 'u_' . $user->getId();
|
||||||
|
|
||||||
|
if (!\array_key_exists($key, $this->latestApprovals)) {
|
||||||
|
$this->latestApprovals[$key] = $this->workingTimeRepository->getLatestApproval($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->latestApprovals[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isApproved(User $user, \DateTimeInterface $dateTime): bool
|
||||||
|
{
|
||||||
|
$latestApproval = $this->getLatestApproval($user);
|
||||||
|
if ($latestApproval === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$latestApprovalDate = $latestApproval->getDate();
|
||||||
|
|
||||||
|
$begin = \DateTimeImmutable::createFromInterface($dateTime);
|
||||||
|
$begin = $begin->setTime(0, 0, 0);
|
||||||
|
|
||||||
|
if ($begin > $latestApprovalDate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getYear(User $user, \DateTimeInterface $yearDate, \DateTimeInterface $until): Year
|
public function getYear(User $user, \DateTimeInterface $yearDate, \DateTimeInterface $until): Year
|
||||||
@@ -124,6 +160,11 @@ final class WorkingTimeService
|
|||||||
|
|
||||||
$this->workingTimeRepository->persistScheduledWorkingTimes();
|
$this->workingTimeRepository->persistScheduledWorkingTimes();
|
||||||
|
|
||||||
|
$key = 'u_' . $user->getId();
|
||||||
|
if (\array_key_exists($key, $this->latestApprovals)) {
|
||||||
|
unset($this->latestApprovals[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
$this->eventDispatcher->dispatch(new WorkingTimeApproveMonthEvent($user, $month, $approvalDate, $approvedBy));
|
$this->eventDispatcher->dispatch(new WorkingTimeApproveMonthEvent($user, $month, $approvalDate, $approvedBy));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -281,20 +281,27 @@
|
|||||||
{%- endblock secret_widget %}
|
{%- endblock secret_widget %}
|
||||||
|
|
||||||
{% block yearpicker_widget -%}
|
{% block yearpicker_widget -%}
|
||||||
|
{% set yearInt = year|date_format('Y') %}
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-left btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ previousYear|date_short }}"
|
<a class="btn btn-left btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ previousYear|date_short }}"
|
||||||
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ previousYear|report_date }}" data-event-bubbles="change">
|
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ previousYear|report_date }}" data-event-bubbles="change">
|
||||||
{{ icon('left') }}
|
{{ icon('left') }}
|
||||||
</a>
|
</a>
|
||||||
<a class="btn" href="#" onclick="return false;">
|
{% if show_range %}
|
||||||
<span id="{{ form.vars.id }}_month_name">
|
<a class="btn" href="#" onclick="return false;">
|
||||||
{% if show_range %}
|
<span id="{{ form.vars.id }}_month_name">{{ year|date_short }} – {{ nextYear|date_short }}</span>
|
||||||
{{ year|date_short }} – {{ nextYear|date_short }}
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ year|date_format('Y') }}
|
<button type="button" class="btn btn-default dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
{% endif %}
|
{{ yearInt }}
|
||||||
</span>
|
</button>
|
||||||
</a>
|
<ul class="dropdown-menu dropdown-menu-start pre-scrollable">
|
||||||
|
{% for i in (yearInt - 5)..(yearInt + 5) %}
|
||||||
|
{% set tmpYear = report_date(i) %}
|
||||||
|
<li><a href="#" data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ tmpYear|report_date }}" data-event-bubbles="change" class="dropdown-item">{{ tmpYear|date_format('Y') }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
<a class="btn btn-right btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ nextYear|date_short }}"
|
<a class="btn btn-right btn-icon" href="#" data-toggle="tooltip" data-placement="top" title="{{ nextYear|date_short }}"
|
||||||
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ nextYear|report_date }}" data-event-bubbles="change">
|
data-form-widget="copy-data" data-target="#{{ form.vars.id }}" data-value="{{ nextYear|report_date }}" data-event-bubbles="change">
|
||||||
{{ icon('right') }}
|
{{ icon('right') }}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{% macro init_frontend_loader() %}
|
{% macro init_frontend_loader() %}
|
||||||
|
{% set fdow = app.user is not null ? app.user.firstDayOfWeek : 'monday' %}
|
||||||
{% set configurations = javascript_configurations(app.user)|merge({
|
{% set configurations = javascript_configurations(app.user)|merge({
|
||||||
login: path('login'),
|
login: path('login'),
|
||||||
direction: tabler_bundle.rightToLeft ? 'rtl' : 'ltr',
|
direction: tabler_bundle.rightToLeft ? 'rtl' : 'ltr',
|
||||||
first_dow_iso: iso_day_by_name(app.user.firstDayOfWeek),
|
first_dow_iso: iso_day_by_name(fdow),
|
||||||
}) %}
|
}) %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'page_setup.html.twig' %}
|
||||||
|
|
||||||
{% block page_class %}quick-entry-page{% endblock %}
|
{% block page_class %}quick-entry-page{% endblock %}
|
||||||
|
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
{% block box_before %}
|
{% block box_before %}
|
||||||
{{ form_start(form, {attr: {id: 'quick-entries-form', class: 'form-dataTable quick-entries'}}) }}
|
{{ form_start(form, {attr: {id: 'quick-entries-form', class: 'form-dataTable quick-entries'}}) }}
|
||||||
{{ form_widget(form._token) }}
|
{{ form_widget(form._token) }}
|
||||||
{{ form_widget(form.date) }}
|
|
||||||
{{ form_errors(form) }}
|
{{ form_errors(form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block box_after %}
|
{% block box_after %}
|
||||||
@@ -107,10 +106,6 @@
|
|||||||
};
|
};
|
||||||
recalculateTotals();
|
recalculateTotals();
|
||||||
|
|
||||||
document.getElementById('{{ form.date.vars.id }}').addEventListener('change', function(ev) {
|
|
||||||
location.href = '{{ path('quick_entry', {'begin': '__BEGIN__'}) }}'.replace('__BEGIN__', ev.target.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('quick-entries-form').addEventListener('click', recalculateTotals);
|
document.getElementById('quick-entries-form').addEventListener('click', recalculateTotals);
|
||||||
document.getElementById('quick-entries-form').addEventListener('change', recalculateTotals);
|
document.getElementById('quick-entries-form').addEventListener('change', recalculateTotals);
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ class QuickEntryControllerTest extends ControllerBaseTest
|
|||||||
public function testIndexAction(): void
|
public function testIndexAction(): void
|
||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser();
|
$client = $this->getClientForAuthenticatedUser();
|
||||||
$this->request($client, '/quick_entry');
|
$this->request($client, '/quick_entry/');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$node = $client->getCrawler()->filter('section.content form[name=quick_entry_form]');
|
$node = $client->getCrawler()->filter('form[name=quick_entry_weekrange_form]');
|
||||||
self::assertEquals(1, $node->filter('div.btn-group.week-picker-btn-group')->count());
|
self::assertEquals(1, $node->filter('div.btn-group.week-picker-btn-group')->count());
|
||||||
|
|
||||||
|
$node = $client->getCrawler()->filter('section.content form[name=quick_entry_form]');
|
||||||
self::assertEquals(1, $node->filter('input.btn-primary[type=submit]')->count());
|
self::assertEquals(1, $node->filter('input.btn-primary[type=submit]')->count());
|
||||||
|
|
||||||
$addBtn = $node->filter('button.btn-success[type=button]');
|
$addBtn = $node->filter('button.btn-success[type=button]');
|
||||||
@@ -62,11 +64,10 @@ class QuickEntryControllerTest extends ControllerBaseTest
|
|||||||
$fixture->setStartDate(new \DateTime('-7 days'));
|
$fixture->setStartDate(new \DateTime('-7 days'));
|
||||||
$this->importFixture($fixture);
|
$this->importFixture($fixture);
|
||||||
|
|
||||||
$this->request($client, '/quick_entry');
|
$this->request($client, '/quick_entry/');
|
||||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||||
|
|
||||||
$node = $client->getCrawler()->filter('section.content form[name=quick_entry_form]');
|
$node = $client->getCrawler()->filter('form[name=quick_entry_form]');
|
||||||
self::assertEquals(1, $node->filter('div.btn-group.week-picker-btn-group')->count());
|
|
||||||
self::assertEquals(1, $node->filter('input.btn-primary[type=submit]')->count());
|
self::assertEquals(1, $node->filter('input.btn-primary[type=submit]')->count());
|
||||||
|
|
||||||
$addBtn = $node->filter('button.btn-success[type=button]');
|
$addBtn = $node->filter('button.btn-success[type=button]');
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ class QuickEntryModelTest extends TestCase
|
|||||||
{
|
{
|
||||||
public function testEmptyModel(): void
|
public function testEmptyModel(): void
|
||||||
{
|
{
|
||||||
$sut = new QuickEntryModel();
|
$user = new User();
|
||||||
|
$sut = new QuickEntryModel($user);
|
||||||
self::assertFalse($sut->isPrototype());
|
self::assertFalse($sut->isPrototype());
|
||||||
self::assertNull($sut->getProject());
|
self::assertNull($sut->getProject());
|
||||||
self::assertNull($sut->getActivity());
|
self::assertNull($sut->getActivity());
|
||||||
self::assertNull($sut->getUser());
|
self::assertEquals($user, $sut->getUser());
|
||||||
self::assertEquals([], $sut->getNewTimesheet());
|
self::assertEquals([], $sut->getNewTimesheet());
|
||||||
self::assertEquals([], $sut->getTimesheets());
|
self::assertEquals([], $sut->getTimesheets());
|
||||||
self::assertNull($sut->getLatestEntry());
|
self::assertNull($sut->getLatestEntry());
|
||||||
@@ -103,7 +104,8 @@ class QuickEntryModelTest extends TestCase
|
|||||||
|
|
||||||
public function testHasExistingTimesheet(): void
|
public function testHasExistingTimesheet(): void
|
||||||
{
|
{
|
||||||
$sut = new QuickEntryModel();
|
$user = new User();
|
||||||
|
$sut = new QuickEntryModel($user);
|
||||||
|
|
||||||
self::assertFalse($sut->hasExistingTimesheet());
|
self::assertFalse($sut->hasExistingTimesheet());
|
||||||
$mock = $this->createMock(Timesheet::class);
|
$mock = $this->createMock(Timesheet::class);
|
||||||
@@ -128,11 +130,12 @@ class QuickEntryModelTest extends TestCase
|
|||||||
|
|
||||||
public function testPrototype(): void
|
public function testPrototype(): void
|
||||||
{
|
{
|
||||||
$sut = new QuickEntryModel();
|
$user = new User();
|
||||||
|
$sut = new QuickEntryModel($user);
|
||||||
$sut->markAsPrototype();
|
$sut->markAsPrototype();
|
||||||
self::assertTrue($sut->isPrototype());
|
self::assertTrue($sut->isPrototype());
|
||||||
|
|
||||||
$sut = new QuickEntryModel();
|
$sut = new QuickEntryModel($user);
|
||||||
$sut->markAsPrototype();
|
$sut->markAsPrototype();
|
||||||
$mock = $this->createMock(Timesheet::class);
|
$mock = $this->createMock(Timesheet::class);
|
||||||
$mock->method('getId')->willReturn(1);
|
$mock->method('getId')->willReturn(1);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace App\Tests\Model;
|
namespace App\Tests\Model;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
use App\Model\QuickEntryModel;
|
use App\Model\QuickEntryModel;
|
||||||
use App\Model\QuickEntryWeek;
|
use App\Model\QuickEntryWeek;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -27,7 +28,7 @@ class QuickEntryWeekTest extends TestCase
|
|||||||
self::assertEquals([], $sut->getRows());
|
self::assertEquals([], $sut->getRows());
|
||||||
|
|
||||||
$rows = [
|
$rows = [
|
||||||
new QuickEntryModel()
|
new QuickEntryModel(new User())
|
||||||
];
|
];
|
||||||
|
|
||||||
$sut->setRows($rows);
|
$sut->setRows($rows);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class ExtensionsTest extends TestCase
|
|||||||
|
|
||||||
public function testGetFunctions(): void
|
public function testGetFunctions(): void
|
||||||
{
|
{
|
||||||
$functions = ['class_name', 'iso_day_by_name', 'random_color'];
|
$functions = ['report_date', 'class_name', 'iso_day_by_name', 'random_color'];
|
||||||
$sut = $this->getSut();
|
$sut = $this->getSut();
|
||||||
$twigFunctions = $sut->getFunctions();
|
$twigFunctions = $sut->getFunctions();
|
||||||
$this->assertCount(\count($functions), $twigFunctions);
|
$this->assertCount(\count($functions), $twigFunctions);
|
||||||
|
|||||||
@@ -483,6 +483,33 @@ class LocaleFormatExtensionsTest extends TestCase
|
|||||||
self::assertEquals($expected, $sut->getJavascriptConfiguration($user));
|
self::assertEquals($expected, $sut->getJavascriptConfiguration($user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testJavascriptConfigurationsForAnonymous(): void
|
||||||
|
{
|
||||||
|
$expected = [
|
||||||
|
'formatDuration' => '%h:%m',
|
||||||
|
'formatDate' => 'Y-m-d',
|
||||||
|
'defaultColor' => '#d2d6de',
|
||||||
|
'twentyFourHours' => false,
|
||||||
|
'updateBrowserTitle' => false,
|
||||||
|
'timezone' => 'Europe/Vienna',
|
||||||
|
'locale' => 'en',
|
||||||
|
'language' => 'en',
|
||||||
|
'user' => [
|
||||||
|
'id' => null,
|
||||||
|
'name' => 'anonymous',
|
||||||
|
'admin' => false,
|
||||||
|
'superAdmin' => false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
|
||||||
|
$sut = new LocaleFormatExtensions(new LocaleService($this->localeEn), $security);
|
||||||
|
$sut->setLocale('en');
|
||||||
|
|
||||||
|
self::assertEquals($expected, $sut->getJavascriptConfiguration());
|
||||||
|
}
|
||||||
|
|
||||||
public function testDurationDecimal(): void
|
public function testDurationDecimal(): void
|
||||||
{
|
{
|
||||||
$record = $this->getTimesheet(9437);
|
$record = $this->getTimesheet(9437);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace App\Tests\Validator\Constraints;
|
|||||||
use App\Entity\Activity;
|
use App\Entity\Activity;
|
||||||
use App\Entity\Project;
|
use App\Entity\Project;
|
||||||
use App\Entity\Timesheet;
|
use App\Entity\Timesheet;
|
||||||
|
use App\Entity\User;
|
||||||
use App\Model\QuickEntryModel as QuickEntryModelEntity;
|
use App\Model\QuickEntryModel as QuickEntryModelEntity;
|
||||||
use App\Validator\Constraints\QuickEntryModel;
|
use App\Validator\Constraints\QuickEntryModel;
|
||||||
use App\Validator\Constraints\QuickEntryModelValidator;
|
use App\Validator\Constraints\QuickEntryModelValidator;
|
||||||
@@ -47,7 +48,8 @@ class QuickEntryModelValidatorTest extends ConstraintValidatorTestCase
|
|||||||
|
|
||||||
public function testTriggersOnMissingProjectAndActivity(): void
|
public function testTriggersOnMissingProjectAndActivity(): void
|
||||||
{
|
{
|
||||||
$model = new QuickEntryModelEntity();
|
$user = new User();
|
||||||
|
$model = new QuickEntryModelEntity($user);
|
||||||
$timesheet = new Timesheet();
|
$timesheet = new Timesheet();
|
||||||
$timesheet->setBegin(new \DateTime());
|
$timesheet->setBegin(new \DateTime());
|
||||||
$timesheet->setBegin(new \DateTime('+ 1 hour'));
|
$timesheet->setBegin(new \DateTime('+ 1 hour'));
|
||||||
@@ -66,7 +68,8 @@ class QuickEntryModelValidatorTest extends ConstraintValidatorTestCase
|
|||||||
|
|
||||||
public function testTriggersOnMissingActivity(): void
|
public function testTriggersOnMissingActivity(): void
|
||||||
{
|
{
|
||||||
$model = new QuickEntryModelEntity();
|
$user = new User();
|
||||||
|
$model = new QuickEntryModelEntity($user);
|
||||||
$model->setProject(new Project());
|
$model->setProject(new Project());
|
||||||
$timesheet = new Timesheet();
|
$timesheet = new Timesheet();
|
||||||
$timesheet->setBegin(new \DateTime());
|
$timesheet->setBegin(new \DateTime());
|
||||||
@@ -83,7 +86,8 @@ class QuickEntryModelValidatorTest extends ConstraintValidatorTestCase
|
|||||||
|
|
||||||
public function testTriggersOnMissingProject(): void
|
public function testTriggersOnMissingProject(): void
|
||||||
{
|
{
|
||||||
$model = new QuickEntryModelEntity();
|
$user = new User();
|
||||||
|
$model = new QuickEntryModelEntity($user);
|
||||||
$model->setActivity(new Activity());
|
$model->setActivity(new Activity());
|
||||||
$timesheet = new Timesheet();
|
$timesheet = new Timesheet();
|
||||||
$timesheet->setBegin(new \DateTime());
|
$timesheet->setBegin(new \DateTime());
|
||||||
@@ -100,7 +104,8 @@ class QuickEntryModelValidatorTest extends ConstraintValidatorTestCase
|
|||||||
|
|
||||||
public function testDoesNotTriggerOnPrototype(): void
|
public function testDoesNotTriggerOnPrototype(): void
|
||||||
{
|
{
|
||||||
$model = new QuickEntryModelEntity();
|
$user = new User();
|
||||||
|
$model = new QuickEntryModelEntity($user);
|
||||||
|
|
||||||
$this->validator->validate($model, new QuickEntryModel());
|
$this->validator->validate($model, new QuickEntryModel());
|
||||||
|
|
||||||
@@ -109,7 +114,8 @@ class QuickEntryModelValidatorTest extends ConstraintValidatorTestCase
|
|||||||
|
|
||||||
public function testDoesNotTriggerOnProperlyFilled(): void
|
public function testDoesNotTriggerOnProperlyFilled(): void
|
||||||
{
|
{
|
||||||
$model = new QuickEntryModelEntity();
|
$user = new User();
|
||||||
|
$model = new QuickEntryModelEntity($user);
|
||||||
$model->setActivity(new Activity());
|
$model->setActivity(new Activity());
|
||||||
$model->setProject(new Project());
|
$model->setProject(new Project());
|
||||||
$timesheet = new Timesheet();
|
$timesheet = new Timesheet();
|
||||||
|
|||||||
Reference in New Issue
Block a user