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:
@@ -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');
|
||||
|
||||
@@ -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
19
composer.lock
generated
@@ -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": {
|
||||
|
||||
@@ -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
2
public/build/app.a6b63f34.js
Normal file
2
public/build/app.a6b63f34.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
|
||||
@@ -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'))]
|
||||
|
||||
@@ -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'))]
|
||||
|
||||
@@ -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'))]
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user