added project filter in user-list reports (#4615)
This commit is contained in:
40
phpstan.neon
40
phpstan.neon
@@ -1032,11 +1032,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Controller/Reporting/CustomerMonthlyProjectsController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$users of method App\\\\Reporting\\\\CustomerMonthlyProjects\\\\CustomerMonthlyProjectsRepository\\:\\:getGroupedByCustomerProjectActivityUser\\(\\) expects array\\<App\\\\Entity\\\\User\\>, iterable\\<App\\\\Entity\\\\User\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Reporting/CustomerMonthlyProjectsController.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||
count: 3
|
||||
@@ -1052,31 +1047,16 @@ parameters:
|
||||
count: 1
|
||||
path: src/Controller/Reporting/ReportUsersMonthController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$users of method App\\\\Timesheet\\\\TimesheetStatisticService\\:\\:getDailyStatistics\\(\\) expects array\\<App\\\\Entity\\\\User\\>, iterable\\<App\\\\Entity\\\\User\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Reporting/ReportUsersMonthController.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Controller\\\\Reporting\\\\ReportUsersWeekController\\:\\:getData\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Reporting/ReportUsersWeekController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$users of method App\\\\Timesheet\\\\TimesheetStatisticService\\:\\:getDailyStatistics\\(\\) expects array\\<App\\\\Entity\\\\User\\>, iterable\\<App\\\\Entity\\\\User\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Reporting/ReportUsersWeekController.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Controller\\\\Reporting\\\\ReportUsersYearController\\:\\:getData\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Reporting/ReportUsersYearController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$users of method App\\\\Timesheet\\\\TimesheetStatisticService\\:\\:getMonthlyStats\\(\\) expects array\\<App\\\\Entity\\\\User\\>, iterable\\<App\\\\Entity\\\\User\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Reporting/ReportUsersYearController.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Controller\\\\Reporting\\\\UserMonthController\\:\\:getData\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -1182,11 +1162,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Controller/TimesheetTeamController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$entries of method App\\\\Export\\\\Spreadsheet\\\\UserExporter\\:\\:export\\(\\) expects array\\<App\\\\Entity\\\\User\\>, iterable\\<App\\\\Entity\\\\User\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/UserController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$plainPassword of method Symfony\\\\Component\\\\PasswordHasher\\\\Hasher\\\\UserPasswordHasherInterface\\:\\:hashPassword\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
@@ -4822,26 +4797,11 @@ parameters:
|
||||
count: 1
|
||||
path: src/Repository/UserRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\UserRepository\\:\\:getHydratedResultsByQuery\\(\\) should return iterable\\<App\\\\Entity\\\\User\\> but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/UserRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\UserRepository\\:\\:saveUser\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/UserRepository.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$identifier of method App\\\\Repository\\\\UserRepository\\:\\:loadUserByIdentifier\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/UserRepository.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$results of method App\\\\Repository\\\\Loader\\\\UserLoader\\:\\:loadResults\\(\\) expects array\\<App\\\\Entity\\\\User\\|int\\>, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/UserRepository.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$message of class Symfony\\\\Component\\\\Security\\\\Core\\\\Exception\\\\AuthenticationException constructor expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -21,6 +21,7 @@ use App\Form\UserPreferencesForm;
|
||||
use App\Form\UserRolesType;
|
||||
use App\Form\UserTeamsType;
|
||||
use App\Form\UserTwoFactorType;
|
||||
use App\Repository\Query\TimesheetStatisticQuery;
|
||||
use App\Repository\TeamRepository;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Repository\UserRepository;
|
||||
@@ -84,13 +85,15 @@ final class ProfileController extends AbstractController
|
||||
// but we need a full year, because the chart needs always 12 month
|
||||
$begin = $dateFactory->createStartOfYear($begin);
|
||||
|
||||
$query = new TimesheetStatisticQuery($begin, $end, [$profile]);
|
||||
|
||||
$viewVars = [
|
||||
'tab' => 'charts',
|
||||
'page_setup' => $this->getPageSetup($profile, 'charts'),
|
||||
'user' => $profile,
|
||||
'stats' => $userStats,
|
||||
'workingSince' => $workStartingDay,
|
||||
'workMonths' => $statisticService->getMonthlyStats($begin, $end, [$profile])[0]
|
||||
'workMonths' => $statisticService->getMonthlyStats($query)[0]
|
||||
];
|
||||
|
||||
return $this->render('user/stats.html.twig', $viewVars);
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Export\Spreadsheet\Writer\XlsxWriter;
|
||||
use App\Model\DailyStatistic;
|
||||
use App\Reporting\MonthlyUserList\MonthlyUserList;
|
||||
use App\Reporting\MonthlyUserList\MonthlyUserListForm;
|
||||
use App\Repository\Query\TimesheetStatisticQuery;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use App\Repository\Query\VisibilityInterface;
|
||||
use App\Repository\UserRepository;
|
||||
@@ -106,7 +107,9 @@ final class ReportUsersMonthController extends AbstractController
|
||||
$hasData = true;
|
||||
|
||||
if (!empty($allUsers)) {
|
||||
$dayStats = $statisticService->getDailyStatistics($start, $end, $allUsers);
|
||||
$statsQuery = new TimesheetStatisticQuery($start, $end, $allUsers);
|
||||
$statsQuery->setProject($values->getProject());
|
||||
$dayStats = $statisticService->getDailyStatistics($statsQuery);
|
||||
}
|
||||
|
||||
if (empty($dayStats)) {
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Export\Spreadsheet\Writer\XlsxWriter;
|
||||
use App\Model\DailyStatistic;
|
||||
use App\Reporting\WeeklyUserList\WeeklyUserList;
|
||||
use App\Reporting\WeeklyUserList\WeeklyUserListForm;
|
||||
use App\Repository\Query\TimesheetStatisticQuery;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use App\Repository\Query\VisibilityInterface;
|
||||
use App\Repository\UserRepository;
|
||||
@@ -102,7 +103,9 @@ final class ReportUsersWeekController extends AbstractController
|
||||
$hasData = true;
|
||||
|
||||
if (!empty($allUsers)) {
|
||||
$dayStats = $statisticService->getDailyStatistics($start, $end, $allUsers);
|
||||
$statsQuery = new TimesheetStatisticQuery($start, $end, $allUsers);
|
||||
$statsQuery->setProject($values->getProject());
|
||||
$dayStats = $statisticService->getDailyStatistics($statsQuery);
|
||||
}
|
||||
|
||||
if (empty($dayStats)) {
|
||||
|
||||
@@ -16,6 +16,7 @@ use App\Export\Spreadsheet\Writer\XlsxWriter;
|
||||
use App\Model\MonthlyStatistic;
|
||||
use App\Reporting\YearlyUserList\YearlyUserList;
|
||||
use App\Reporting\YearlyUserList\YearlyUserListForm;
|
||||
use App\Repository\Query\TimesheetStatisticQuery;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use App\Repository\Query\VisibilityInterface;
|
||||
use App\Repository\UserRepository;
|
||||
@@ -107,7 +108,9 @@ final class ReportUsersYearController extends AbstractController
|
||||
$hasData = true;
|
||||
|
||||
if (!empty($allUsers)) {
|
||||
$monthStats = $statisticService->getMonthlyStats($start, $end, $allUsers);
|
||||
$statsQuery = new TimesheetStatisticQuery($start, $end, $allUsers);
|
||||
$statsQuery->setProject($values->getProject());
|
||||
$monthStats = $statisticService->getMonthlyStats($statsQuery);
|
||||
}
|
||||
|
||||
if (empty($monthStats)) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Reporting;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Team;
|
||||
|
||||
abstract class AbstractUserList
|
||||
@@ -17,6 +18,7 @@ abstract class AbstractUserList
|
||||
private bool $decimal = false;
|
||||
private string $sumType = 'duration';
|
||||
private ?Team $team = null;
|
||||
private ?Project $project = null;
|
||||
|
||||
public function getDate(): ?\DateTimeInterface
|
||||
{
|
||||
@@ -61,4 +63,14 @@ abstract class AbstractUserList
|
||||
{
|
||||
$this->team = $team;
|
||||
}
|
||||
|
||||
public function getProject(): ?Project
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
public function setProject(?Project $project): void
|
||||
{
|
||||
$this->project = $project;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Reporting\MonthlyUserList;
|
||||
|
||||
use App\Form\Type\MonthPickerType;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\ReportSumType;
|
||||
use App\Form\Type\TeamType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
@@ -33,6 +34,11 @@ final class MonthlyUserListForm extends AbstractType
|
||||
'required' => false,
|
||||
'width' => false,
|
||||
]);
|
||||
$builder->add('project', ProjectType::class, [
|
||||
'multiple' => false,
|
||||
'required' => false,
|
||||
'width' => false,
|
||||
]);
|
||||
$builder->add('sumType', ReportSumType::class);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Reporting\WeeklyUserList;
|
||||
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\ReportSumType;
|
||||
use App\Form\Type\TeamType;
|
||||
use App\Form\Type\WeekPickerType;
|
||||
@@ -33,6 +34,11 @@ final class WeeklyUserListForm extends AbstractType
|
||||
'required' => false,
|
||||
'width' => false,
|
||||
]);
|
||||
$builder->add('project', ProjectType::class, [
|
||||
'multiple' => false,
|
||||
'required' => false,
|
||||
'width' => false,
|
||||
]);
|
||||
$builder->add('sumType', ReportSumType::class);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Reporting\YearlyUserList;
|
||||
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\ReportSumType;
|
||||
use App\Form\Type\TeamType;
|
||||
use App\Form\Type\YearPickerType;
|
||||
@@ -34,6 +35,11 @@ final class YearlyUserListForm extends AbstractType
|
||||
'required' => false,
|
||||
'width' => false,
|
||||
]);
|
||||
$builder->add('project', ProjectType::class, [
|
||||
'multiple' => false,
|
||||
'required' => false,
|
||||
'width' => false,
|
||||
]);
|
||||
$builder->add('sumType', ReportSumType::class);
|
||||
}
|
||||
|
||||
|
||||
57
src/Repository/Query/TimesheetStatisticQuery.php
Normal file
57
src/Repository/Query/TimesheetStatisticQuery.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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\Repository\Query;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
|
||||
final class TimesheetStatisticQuery
|
||||
{
|
||||
private ?Project $project = null;
|
||||
|
||||
/**
|
||||
* @param array<User> $users
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly \DateTimeInterface $begin,
|
||||
private readonly \DateTimeInterface $end,
|
||||
private array $users
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function getBegin(): \DateTimeInterface
|
||||
{
|
||||
return $this->begin;
|
||||
}
|
||||
|
||||
public function getEnd(): \DateTimeInterface
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User[]
|
||||
*/
|
||||
public function getUsers(): array
|
||||
{
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
public function getProject(): ?Project
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
public function setProject(?Project $project): void
|
||||
{
|
||||
$this->project = $project;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
||||
* @throws ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
public function saveUser(User $user)
|
||||
public function saveUser(User $user): void
|
||||
{
|
||||
$entityManager = $this->getEntityManager();
|
||||
$entityManager->persist($user);
|
||||
@@ -397,7 +397,7 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
||||
* @param UserQuery $query
|
||||
* @return User[]
|
||||
*/
|
||||
public function getUsersForQuery(UserQuery $query): iterable
|
||||
public function getUsersForQuery(UserQuery $query): array
|
||||
{
|
||||
$qb = $this->getQueryBuilderForQuery($query);
|
||||
|
||||
@@ -408,8 +408,9 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
||||
* @param QueryBuilder $qb
|
||||
* @return User[]
|
||||
*/
|
||||
protected function getHydratedResultsByQuery(QueryBuilder $qb): iterable
|
||||
protected function getHydratedResultsByQuery(QueryBuilder $qb): array
|
||||
{
|
||||
/** @var array<User> $results */
|
||||
$results = $qb->getQuery()->getResult();
|
||||
|
||||
$loader = new UserLoader($qb->getEntityManager());
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace App\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Model\DailyStatistic;
|
||||
use App\Model\MonthlyStatistic;
|
||||
use App\Repository\Query\TimesheetStatisticQuery;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class TimesheetStatisticService
|
||||
@@ -23,13 +23,15 @@ final class TimesheetStatisticService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTimeInterface $begin
|
||||
* @param DateTimeInterface $end
|
||||
* @param User[] $users
|
||||
* @return DailyStatistic[]
|
||||
*/
|
||||
public function getDailyStatistics(DateTimeInterface $begin, DateTimeInterface $end, array $users): array
|
||||
public function getDailyStatistics(TimesheetStatisticQuery $query): array
|
||||
{
|
||||
$begin = $query->getBegin();
|
||||
$end = $query->getEnd();
|
||||
$users = $query->getUsers();
|
||||
$project = $query->getProject();
|
||||
|
||||
/** @var DailyStatistic[] $stats */
|
||||
$stats = [];
|
||||
|
||||
@@ -63,6 +65,13 @@ final class TimesheetStatisticService
|
||||
->addGroupBy('billable')
|
||||
;
|
||||
|
||||
if ($project !== null) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->eq('t.project', ':project'))
|
||||
->setParameter('project', $project)
|
||||
;
|
||||
}
|
||||
|
||||
$results = $qb->getQuery()->getResult();
|
||||
|
||||
foreach ($results as $row) {
|
||||
@@ -241,7 +250,7 @@ final class TimesheetStatisticService
|
||||
return $stats;
|
||||
}
|
||||
|
||||
public function findFirstRecordDate(User $user): ?DateTime
|
||||
public function findFirstRecordDate(User $user): ?\DateTimeImmutable
|
||||
{
|
||||
$result = $this->repository->createQueryBuilder('t')
|
||||
->select('MIN(t.begin)')
|
||||
@@ -254,19 +263,19 @@ final class TimesheetStatisticService
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DateTime((string) $result, new \DateTimeZone($user->getTimezone()));
|
||||
return new \DateTimeImmutable((string) $result, new \DateTimeZone($user->getTimezone()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Year statistics.
|
||||
*
|
||||
* @param DateTime $begin
|
||||
* @param DateTime $end
|
||||
* @param User[] $users
|
||||
* @return MonthlyStatistic[]
|
||||
*/
|
||||
public function getMonthlyStats(DateTime $begin, DateTime $end, array $users): array
|
||||
public function getMonthlyStats(TimesheetStatisticQuery $query): array
|
||||
{
|
||||
$begin = $query->getBegin();
|
||||
$end = $query->getEnd();
|
||||
$users = $query->getUsers();
|
||||
$project = $query->getProject();
|
||||
|
||||
/** @var MonthlyStatistic[] $stats */
|
||||
$stats = [];
|
||||
|
||||
@@ -297,6 +306,13 @@ final class TimesheetStatisticService
|
||||
->addGroupBy('billable')
|
||||
;
|
||||
|
||||
if ($project !== null) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->eq('t.project', ':project'))
|
||||
->setParameter('project', $project)
|
||||
;
|
||||
}
|
||||
|
||||
$results = $qb->getQuery()->getResult();
|
||||
|
||||
foreach ($results as $row) {
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
{% if form.team is defined %}
|
||||
{{ form_widget(form.team, {'label': false, 'placeholder': 'please_choose'}) }}
|
||||
{% endif %}
|
||||
{% if form.project is defined %}
|
||||
{{ form_widget(form.project, {'label': false, 'placeholder': 'please_choose'}) }}
|
||||
{% endif %}
|
||||
{{ form_widget(form.sumType) }}
|
||||
{% from '@theme/components/buttons.html.twig' import submit_button %}
|
||||
{{ submit_button('download', {'attr': {'formaction': path(export_route)}, 'icon': 'download', 'combined': false}, 'primary') }}
|
||||
|
||||
Reference in New Issue
Block a user