Release 2.0.12 (#3947)

- added submenus in action drodowns, to shorten them
- show all user-edit-screens in action dropdown
- fix cascade delete teams through customer
- fix cascade delete customer/project/activity through teams
- fix responsive classes for "internal rate" column
- clarify error message if invoice number generator or calculator is missing
This commit is contained in:
Kevin Papst
2023-03-24 00:47:36 +01:00
committed by GitHub
parent 3a5d7a62de
commit 04b5eb4c38
19 changed files with 54 additions and 49 deletions

View File

@@ -76,7 +76,10 @@ export default class KimaiContextMenu {
dropdownElement.style.top = (event.clientY) + 'px';
dropdownElement.style.left = (event.clientX) + 'px';
const dropdownListener = () => {
const dropdownListener = (event) => {
if (event.target.classList.contains('dropdown-toggle') || event.target.classList.contains('dropdown-divider')) {
return;
}
dropdownElement.classList.remove('d-block');
if (!dropdownElement.classList.contains('d-none')) {
dropdownElement.classList.add('d-none');

View File

@@ -34,7 +34,7 @@
"friendsofsymfony/rest-bundle": "^3.0",
"gedmo/doctrine-extensions": "^3.6",
"jms/serializer-bundle": "^5.0",
"kevinpapst/tabler-bundle": "^0.13",
"kevinpapst/tabler-bundle": "dev-main",
"league/csv": "^9.4",
"mpdf/mpdf": "^8.0",
"nelmio/api-doc-bundle": "^4.0",

19
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "42733cc81b93bcae0a70454796d6c486",
"content-hash": "dfc85100677cacc71615a6442646ff39",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -2415,16 +2415,16 @@
},
{
"name": "kevinpapst/tabler-bundle",
"version": "0.13",
"version": "dev-main",
"source": {
"type": "git",
"url": "https://github.com/kevinpapst/TablerBundle.git",
"reference": "28287dc810186ef3188811af071cb67832e7c7f0"
"reference": "f0a8cdae6b4a243ee9acce60e2ffbb5f0c107c03"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kevinpapst/TablerBundle/zipball/28287dc810186ef3188811af071cb67832e7c7f0",
"reference": "28287dc810186ef3188811af071cb67832e7c7f0",
"url": "https://api.github.com/repos/kevinpapst/TablerBundle/zipball/f0a8cdae6b4a243ee9acce60e2ffbb5f0c107c03",
"reference": "f0a8cdae6b4a243ee9acce60e2ffbb5f0c107c03",
"shasum": ""
},
"require": {
@@ -2454,6 +2454,7 @@
"suggest": {
"knplabs/knp-menu-bundle": "Allows easy menu integration"
},
"default-branch": true,
"type": "symfony-bundle",
"autoload": {
"psr-4": {
@@ -2473,7 +2474,7 @@
"description": "Admin/Backend theme bundle for Symfony based on Tabler.io",
"support": {
"issues": "https://github.com/kevinpapst/TablerBundle/issues",
"source": "https://github.com/kevinpapst/TablerBundle/tree/0.13"
"source": "https://github.com/kevinpapst/TablerBundle/tree/main"
},
"funding": [
{
@@ -2485,7 +2486,7 @@
"type": "github"
}
],
"time": "2023-03-21T09:35:32+00:00"
"time": "2023-03-23T14:18:33+00:00"
},
{
"name": "laminas/laminas-escaper",
@@ -13344,7 +13345,9 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {
"kevinpapst/tabler-bundle": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View File

@@ -7150,11 +7150,6 @@ parameters:
count: 1
path: src/Validator/Constraints/UserValidator.php
-
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\User\\|null\\.$#"
count: 1
path: src/Voter/TimesheetVoter.php
-
message: "#^Cannot call method isVisible\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
count: 1

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@
"app": {
"js": [
"/build/runtime.f0079159.js",
"/build/app.4ca3f16e.js"
"/build/app.a6b63f34.js"
],
"css": [
"/build/app.b10b16e5.css"
@@ -45,7 +45,7 @@
"calendar": {
"js": [
"/build/runtime.f0079159.js",
"/build/calendar.94fe23d5.js"
"/build/calendar.8577d9cd.js"
],
"css": [
"/build/calendar.bb473428.css"
@@ -63,7 +63,7 @@
},
"integrity": {
"/build/runtime.f0079159.js": "sha384-H22sAW1aTvyIPqvHOvGXWSWTxf0y6mptp+MsVmyXCfjx/WJjBbhX9gbUZ+qIuihV",
"/build/app.4ca3f16e.js": "sha384-4VmfUWQxO4OK+j7bPm7p+B8b/krMYs5T+JLrU6DPiTep/LNoMLq11DGP7QL0j7Pu",
"/build/app.a6b63f34.js": "sha384-gYC58lsaQMGHSI811cimIwsqDcwhnKasGWsvFQODG0FrxVb2dDV9VvtFo9V5I8JL",
"/build/app.b10b16e5.css": "sha384-dwEgmHAQRm4PP7XTYkJfIoB+Iumd+0WuH5FyoC/+qC3WQ/e1gCTmlx9M2mIBxPuK",
"/build/export-pdf.587575e7.js": "sha384-J50GStmmfVwUTN4dIRQ02eg9hyzGFPSzpTtpPody92j0V6zCqw+s5l8+ZhVTugeW",
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
@@ -72,7 +72,7 @@
"/build/invoice-pdf.d86b82ee.js": "sha384-A0HJqP+MvEqQr1uG8wViCeEWxBRKyS6l8D+Ao4pFYHUA12gCC1gRYhk9I+SJPvZq",
"/build/invoice-pdf.c88953bb.css": "sha384-ZvSi1e+ZKGzvZJUtAPLjzOSTh13N9zRevq44GKdYdBja/DAplGE55saY2Ur+83yv",
"/build/chart.f5becfac.js": "sha384-GSqETm8wULiVXyizvwRompfwu63r/C0Qd/AvrHDE4cqAKiIGCssb3QyBtGu1WN+W",
"/build/calendar.94fe23d5.js": "sha384-7STPDBwT7nqGf6WWeoWJ2/KyDsf8J7cs9hw/zcYQefgKlzfexbxdZsIJxFxoK1Bu",
"/build/calendar.8577d9cd.js": "sha384-1qlF+4tr7/zve+wvZef7sVcbUxhK0/yEm5Hbuct8K/ZV4JtENdNY7YRLqOcZ/8Y3",
"/build/calendar.bb473428.css": "sha384-900W9o8666Qpw21rLP7hn5Ql8tWd40k0IcE3QpFm7E7V6bKva7xQgGlyzVuuh4GK",
"/build/dashboard.6774a712.js": "sha384-lBwkNqUPv+IBbznagiFakY2BOIueq/Bg89wHO2XeM9YAZ51rPkrvdMd+XmFupHsM",
"/build/dashboard.18f5a8b7.css": "sha384-PBD8ftb2yBoSjRe2sN5Xb4dEz045kOEUfcIufdSis+tWoWdAx5j4Yic+F/6CYbrP"

View File

@@ -1,6 +1,6 @@
{
"build/app.css": "/build/app.b10b16e5.css",
"build/app.js": "/build/app.4ca3f16e.js",
"build/app.js": "/build/app.a6b63f34.js",
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
"build/export-pdf.js": "/build/export-pdf.587575e7.js",
"build/invoice.css": "/build/invoice.3c80ee80.css",
@@ -9,7 +9,7 @@
"build/invoice-pdf.js": "/build/invoice-pdf.d86b82ee.js",
"build/chart.js": "/build/chart.f5becfac.js",
"build/calendar.css": "/build/calendar.bb473428.css",
"build/calendar.js": "/build/calendar.94fe23d5.js",
"build/calendar.js": "/build/calendar.8577d9cd.js",
"build/dashboard.css": "/build/dashboard.18f5a8b7.css",
"build/dashboard.js": "/build/dashboard.6774a712.js",
"build/runtime.js": "/build/runtime.f0079159.js"

View File

@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '2.0.11';
public const VERSION = '2.0.12';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 20011;
public const VERSION_ID = 20012;
/**
* The software name
*/

View File

@@ -88,7 +88,7 @@ abstract class TimesheetAbstractController extends AbstractController
if ($canSeeRate) {
$table->addColumn('hourlyRate', ['class' => 'text-end d-none text-nowrap']);
$table->addColumn('internalRate', ['class' => 'text-end text-nowrap']);
$table->addColumn('internalRate', ['class' => 'text-end text-nowrap d-none d-md-table-cell']);
$table->addColumn('rate', ['class' => 'text-end text-nowrap']);
}

View File

@@ -99,10 +99,10 @@ class Activity implements EntityWithMetaFields, EntityWithBudget
*
* @var Collection<Team>
*/
#[ORM\ManyToMany(targetEntity: 'App\Entity\Team', cascade: ['persist'], inversedBy: 'activities')]
#[ORM\JoinTable(name: 'kimai2_activities_teams')]
#[ORM\JoinColumn(name: 'activity_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
#[ORM\InverseJoinColumn(name: 'team_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
#[ORM\ManyToMany(targetEntity: 'App\Entity\Team', cascade: ['persist'], inversedBy: 'activities')]
#[Serializer\Expose]
#[Serializer\Groups(['Activity'])]
#[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Team'))]

View File

@@ -171,7 +171,7 @@ class Customer implements EntityWithMetaFields, EntityWithBudget
#[ORM\JoinTable(name: 'kimai2_customers_teams')]
#[ORM\JoinColumn(name: 'customer_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
#[ORM\InverseJoinColumn(name: 'team_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
#[ORM\ManyToMany(targetEntity: 'App\Entity\Team', cascade: ['persist', 'remove'], inversedBy: 'customers')]
#[ORM\ManyToMany(targetEntity: 'App\Entity\Team', cascade: ['persist'], inversedBy: 'customers')]
#[Serializer\Expose]
#[Serializer\Groups(['Customer'])]
#[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Team'))]

View File

@@ -47,7 +47,7 @@ class Team
*
* @var Collection<TeamMember>
*/
#[ORM\OneToMany(targetEntity: 'App\Entity\TeamMember', mappedBy: 'team', fetch: 'LAZY', cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ORM\OneToMany(targetEntity: TeamMember::class, mappedBy: 'team', fetch: 'LAZY', cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ORM\JoinColumn(onDelete: 'CASCADE')]
#[Assert\Count(min: 1)]
#[Serializer\Expose]
@@ -59,7 +59,7 @@ class Team
*
* @var Collection<Customer>
*/
#[ORM\ManyToMany(targetEntity: 'App\Entity\Customer', mappedBy: 'teams', fetch: 'EXTRA_LAZY', cascade: ['persist', 'remove'])]
#[ORM\ManyToMany(targetEntity: Customer::class, mappedBy: 'teams', fetch: 'EXTRA_LAZY', cascade: ['persist'])]
#[Serializer\Expose]
#[Serializer\Groups(['Team_Entity'])]
#[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Customer'))]
@@ -69,7 +69,7 @@ class Team
*
* @var Collection<Project>
*/
#[ORM\ManyToMany(targetEntity: 'App\Entity\Project', mappedBy: 'teams', fetch: 'EXTRA_LAZY', cascade: ['persist', 'remove'])]
#[ORM\ManyToMany(targetEntity: Project::class, mappedBy: 'teams', fetch: 'EXTRA_LAZY', cascade: ['persist'])]
#[Serializer\Expose]
#[Serializer\Groups(['Team_Entity', 'Expanded'])]
#[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Project'))]
@@ -79,7 +79,7 @@ class Team
*
* @var Collection<Activity>
*/
#[ORM\ManyToMany(targetEntity: 'App\Entity\Activity', mappedBy: 'teams', fetch: 'EXTRA_LAZY', cascade: ['persist', 'remove'])]
#[ORM\ManyToMany(targetEntity: Activity::class, mappedBy: 'teams', fetch: 'EXTRA_LAZY', cascade: ['persist'])]
#[Serializer\Expose]
#[Serializer\Groups(['Team_Entity', 'Expanded'])]
#[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Activity'))]

View File

@@ -11,9 +11,17 @@ namespace App\EventSubscriber\Actions;
use App\Entity\User;
use App\Event\PageActionsEvent;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
final class UserSubscriber extends AbstractActionsSubscriber
{
public function __construct(AuthorizationCheckerInterface $auth, UrlGeneratorInterface $urlGenerator, private EventDispatcherInterface $eventDispatcher)
{
parent::__construct($auth, $urlGenerator);
}
public static function getActionName(): string
{
return 'user';
@@ -23,10 +31,7 @@ final class UserSubscriber extends AbstractActionsSubscriber
{
$payload = $event->getPayload();
/** @var User $user */
$user = $payload['user'];
if ($user->getId() === null) {
if (($user = $payload['user']) === null || !$user instanceof User || $user->getId() === null) {
return;
}
@@ -35,21 +40,20 @@ final class UserSubscriber extends AbstractActionsSubscriber
$event->addDivider();
}
if ($this->isGranted('edit', $user)) {
$event->addAction('edit', ['url' => $this->path('user_profile_edit', ['username' => $user->getUserIdentifier()]), 'title' => 'edit', 'translation_domain' => 'actions']);
$subEvent = new PageActionsEvent($user, ['user' => $user], 'user_forms', 'index');
$this->eventDispatcher->dispatch($subEvent, $subEvent->getEventName());
foreach ($subEvent->getActions() as $id => $action) {
$event->addActionToSubmenu('edit', $id, $action);
}
if ($this->isGranted('preferences', $user)) {
$event->addConfig($this->path('user_profile_preferences', ['username' => $user->getUserIdentifier()]));
}
if ($this->isGranted('report:other') || ($this->isGranted('report:user') && $event->getUser()->getId() === $user->getId())) {
if (($event->getUser()->getId() === $user->getId() && $this->isGranted('report:user')) || $this->isGranted('report:other')) {
$event->addActionToSubmenu('report', 'weekly', ['url' => $this->path('report_user_week', ['user' => $user->getId()]), 'translation_domain' => 'reporting', 'title' => 'report_user_week']);
$event->addActionToSubmenu('report', 'monthly', ['url' => $this->path('report_user_month', ['user' => $user->getId()]), 'translation_domain' => 'reporting', 'title' => 'report_user_month']);
$event->addActionToSubmenu('report', 'yearly', ['url' => $this->path('report_user_year', ['user' => $user->getId()]), 'translation_domain' => 'reporting', 'title' => 'report_user_year']);
}
if ($this->isGranted('view_other_timesheet') && $user->isEnabled()) {
if ($user->isEnabled() && $this->isGranted('view_other_timesheet')) {
$event->addActionToSubmenu('filter', 'timesheet', ['url' => $this->path('admin_timesheet', ['users[]' => $user->getId()]), 'title' => 'timesheet.filter', 'translation_domain' => 'actions']);
}

View File

@@ -421,12 +421,12 @@ final class ServiceInvoice
$generator = $this->getNumberGeneratorByName($template->getNumberGenerator());
if (null === $generator) {
throw new \Exception('Unknown number generator: ' . $template->getNumberGenerator());
throw new \Exception('Please adjust your invoice template, the number generator is invalid: ' . $template->getNumberGenerator());
}
$calculator = $this->getCalculatorByName($template->getCalculator());
if (null === $calculator) {
throw new \Exception('Unknown invoice calculator: ' . $template->getCalculator());
throw new \Exception('Please adjust your invoice template, the invoice calculator is invalid: ' . $template->getCalculator());
}
$model->setCalculator($calculator);

View File

@@ -128,7 +128,7 @@ final class TimesheetVoter extends Voter
$permission .= '_';
// extend me for "team" support later on
if ($subject->getUser()->getId() === $user->getId()) {
if ($subject->getUser()?->getId() === $user->getId()) {
$permission .= 'own';
} else {
$permission .= 'other';