added search modal for timesheet export (#2728)

This commit is contained in:
Kevin Papst
2021-08-24 19:01:48 +02:00
committed by GitHub
parent f6a82ba119
commit f743503705
40 changed files with 526 additions and 186 deletions

View File

@@ -166,6 +166,31 @@ abstract class AbstractController extends BaseAbstractController implements Serv
return new LocaleFormats($this->container->get(LanguageFormattings::class), $locale);
}
private function getLastSearch(BaseQuery $query): ?array
{
$name = 'search_' . $this->getSearchName($query);
if (!$this->get('session')->has($name)) {
return null;
}
return $this->get('session')->get($name);
}
private function getSearchName(BaseQuery $query): string
{
return substr($query->getName(), 0, 50);
}
/**
* @param Request $request
* @internal
*/
protected function ignorePersistedSearch(Request $request): void
{
$request->query->set('performSearch', true);
}
protected function handleSearch(FormInterface $form, Request $request): bool
{
$data = $form->getData();
@@ -173,23 +198,35 @@ abstract class AbstractController extends BaseAbstractController implements Serv
throw new \InvalidArgumentException('handleSearchForm() requires an instanceof BaseQuery as form data');
}
/** @var BookmarkRepository $bookmarkRepo */
$bookmarkRepo = $this->getDoctrine()->getRepository(Bookmark::class);
$bookmark = $bookmarkRepo->getSearchDefaultOptions($this->getUser(), $data->getName());
$submitData = $request->query->all();
// remove bookmark
if ($bookmark !== null && $request->query->has('removeDefaultQuery')) {
$bookmarkRepo->deleteBookmark($bookmark);
return true;
// allow to use forms with block-prefix
if (!empty($formName = $form->getConfig()->getName()) && $request->request->has($formName)) {
$submitData = $request->request->get($formName);
}
// apply bookmark ONLY if search form was not submitted manually
if ($bookmark !== null && !$request->query->has('performSearch')) {
$data->setBookmark($bookmark);
if (!$request->query->has('setDefaultQuery')) {
$searchName = $this->getSearchName($data);
/** @var BookmarkRepository $bookmarkRepo */
$bookmarkRepo = $this->getDoctrine()->getRepository(Bookmark::class);
$bookmark = $bookmarkRepo->getSearchDefaultOptions($this->getUser(), $searchName);
if ($bookmark !== null) {
if ($request->query->has('removeDefaultQuery')) {
$bookmarkRepo->deleteBookmark($bookmark);
$bookmark = null;
return true;
} else {
$data->setBookmark($bookmark);
}
}
// apply persisted search data ONLY if search form was not submitted manually
if (!$request->query->has('performSearch')) {
$sessionSearch = $this->getLastSearch($data);
if ($sessionSearch !== null) {
$submitData = array_merge($sessionSearch, $submitData);
} elseif ($bookmark !== null && !$request->query->has('setDefaultQuery')) {
$submitData = array_merge($bookmark->getContent(), $submitData);
}
}
@@ -205,24 +242,39 @@ abstract class AbstractController extends BaseAbstractController implements Serv
if (!$form->isValid()) {
$data->resetByFormError($form->getErrors(true));
} elseif ($request->query->has('setDefaultQuery')) {
$params = [];
foreach ($form->all() as $name => $child) {
$params[$name] = $child->getViewData();
}
$filter = ['page', 'setDefaultQuery', 'removeDefaultQuery', 'performSearch'];
foreach ($filter as $name) {
if (isset($params[$name])) {
unset($params[$name]);
}
}
return false;
}
$params = [];
foreach ($form->all() as $name => $child) {
$params[$name] = $child->getViewData();
}
// these should NEVER be saved
$filter = ['setDefaultQuery', 'removeDefaultQuery', 'performSearch'];
foreach ($filter as $name) {
if (isset($params[$name])) {
unset($params[$name]);
}
}
$this->get('session')->set('search_' . $searchName, $params);
// filter stuff, that does not belong in a bookmark
$filter = ['page'];
foreach ($filter as $name) {
if (isset($params[$name])) {
unset($params[$name]);
}
}
if ($request->query->has('setDefaultQuery')) {
if ($bookmark === null) {
$bookmark = new Bookmark();
$bookmark->setType(Bookmark::SEARCH_DEFAULT);
$bookmark->setUser($this->getUser());
$bookmark->setName(substr($data->getName(), 0, 50));
$bookmark->setName($searchName);
}
$bookmark->setContent($params);

View File

@@ -23,6 +23,7 @@ use App\Form\MultiUpdate\MultiUpdateTableDTO;
use App\Form\MultiUpdate\TimesheetMultiUpdate;
use App\Form\MultiUpdate\TimesheetMultiUpdateDTO;
use App\Form\TimesheetEditForm;
use App\Form\Toolbar\TimesheetExportToolbarForm;
use App\Form\Toolbar\TimesheetToolbarForm;
use App\Repository\ActivityRepository;
use App\Repository\ProjectRepository;
@@ -33,6 +34,7 @@ use App\Timesheet\TimesheetService;
use App\Timesheet\TrackingMode\TrackingModeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -47,10 +49,6 @@ abstract class TimesheetAbstractController extends AbstractController
* @var EventDispatcherInterface
*/
protected $dispatcher;
/**
* @var ServiceExport
*/
protected $exportService;
/**
* @var TimesheetService
*/
@@ -63,13 +61,11 @@ abstract class TimesheetAbstractController extends AbstractController
public function __construct(
TimesheetRepository $repository,
EventDispatcherInterface $dispatcher,
ServiceExport $exportService,
TimesheetService $timesheetService,
SystemConfiguration $configuration
) {
$this->repository = $repository;
$this->dispatcher = $dispatcher;
$this->exportService = $exportService;
$this->service = $timesheetService;
$this->configuration = $configuration;
}
@@ -241,40 +237,53 @@ abstract class TimesheetAbstractController extends AbstractController
]);
}
protected function export(Request $request, string $exporterId): Response
protected function export(Request $request, ServiceExport $serviceExport): Response
{
$query = new TimesheetQuery();
$query = $this->createDefaultQuery();
$form = $this->getToolbarForm($query);
$form->setData($query);
$form->submit($request->query->all(), false);
$form = $this->getExportForm($query);
$factory = $this->getDateTimeFactory();
// by default the current month is exported, but it can be overwritten
// this should not be removed, otherwise we would export EVERY available record in the admin section
// as the default toolbar query does neither limit the user nor the date-range!
if (null === $query->getBegin()) {
$query->setBegin($factory->getStartOfMonth());
if ($request->isMethod(Request::METHOD_POST)) {
$this->ignorePersistedSearch($request);
}
$query->getBegin()->setTime(0, 0, 0);
if (null === $query->getEnd()) {
$query->setEnd($factory->getEndOfMonth());
if ($this->handleSearch($form, $request)) {
return $this->redirectToRoute($this->getExportRoute());
}
$query->getEnd()->setTime(23, 59, 59);
$this->prepareQuery($query);
$entries = $this->repository->getTimesheetsForQuery($query);
$exporter = $this->exportService->getTimesheetExporterById($exporterId);
if (null === $exporter) {
throw $this->createNotFoundException('Invalid timesheet exporter given');
// make sure that we use the "expected time range"
if (null !== $query->getBegin()) {
$query->getBegin()->setTime(0, 0, 0);
}
if (null !== $query->getEnd()) {
$query->getEnd()->setTime(23, 59, 59);
}
return $exporter->render($entries, $query);
$entries = $this->repository->getTimesheetResult($query);
$stats = $entries->getStatistic();
// perform the real export
if ($request->isMethod(Request::METHOD_POST)) {
$type = $request->request->get('exporter');
if (null !== $type) {
$exporter = $serviceExport->getTimesheetExporterById($type);
if (null === $exporter) {
$form->addError(new FormError('Invalid timesheet exporter given'));
} else {
return $exporter->render($entries->getResults(true), $query);
}
}
}
return $this->render('timesheet/layout-export.html.twig', [
'form' => $form->createView(),
'route_back' => $this->getTimesheetRoute(),
'exporter' => $serviceExport->getTimesheetExporter(),
'stats' => $stats,
]);
}
protected function multiUpdate(Request $request, string $renderTemplate)
@@ -518,6 +527,16 @@ abstract class TimesheetAbstractController extends AbstractController
]);
}
protected function getExportForm(TimesheetQuery $query): FormInterface
{
return $this->createForm(TimesheetExportToolbarForm::class, $query, [
'action' => $this->generateUrl($this->getExportRoute()),
'timezone' => $this->getDateTimeFactory()->getTimezone()->getName(),
'method' => Request::METHOD_POST,
'include_user' => $this->includeUserInForms('toolbar'),
]);
}
protected function getPermissionEditExport(): string
{
return 'edit_export_own_timesheet';
@@ -563,11 +582,29 @@ abstract class TimesheetAbstractController extends AbstractController
return 'timesheet_multi_delete';
}
protected function getExportRoute(): string
{
return 'timesheet_export';
}
protected function canSeeStartEndTime(): bool
{
return $this->getTrackingMode()->canSeeBeginAndEndTimes();
}
protected function getQueryNamePrefix(): string
{
return 'MyTimes';
}
protected function createDefaultQuery(string $suffix = 'Listing'): TimesheetQuery
{
$query = new TimesheetQuery();
$query->setName($this->getQueryNamePrefix() . $suffix);
return $query;
}
abstract protected function getDuplicateForm(Timesheet $entry): FormInterface;
abstract protected function getCreateForm(Timesheet $entry): FormInterface;

View File

@@ -11,10 +11,10 @@ namespace App\Controller;
use App\Entity\Timesheet;
use App\Event\TimesheetMetaDisplayEvent;
use App\Export\ServiceExport;
use App\Form\TimesheetEditForm;
use App\Repository\ActivityRepository;
use App\Repository\ProjectRepository;
use App\Repository\Query\TimesheetQuery;
use App\Repository\TagRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\Form\FormInterface;
@@ -35,20 +35,19 @@ class TimesheetController extends TimesheetAbstractController
*/
public function indexAction(int $page, Request $request): Response
{
$query = new TimesheetQuery();
$query = $this->createDefaultQuery();
$query->setPage($page);
$query->setName('MyTimesListing');
return $this->index($query, $request, 'timesheet', 'timesheet/index.html.twig', TimesheetMetaDisplayEvent::TIMESHEET);
}
/**
* @Route(path="/export/{exporter}", name="timesheet_export", methods={"GET"})
* @Route(path="/export/", name="timesheet_export", methods={"GET", "POST"})
* @Security("is_granted('export_own_timesheet')")
*/
public function exportAction(Request $request, string $exporter): Response
public function exportAction(Request $request, ServiceExport $serviceExport): Response
{
return $this->export($request, $exporter);
return $this->export($request, $serviceExport);
}
/**

View File

@@ -14,6 +14,7 @@ use App\Entity\Team;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Event\TimesheetMetaDisplayEvent;
use App\Export\ServiceExport;
use App\Form\Model\MultiUserTimesheet;
use App\Form\TimesheetAdminEditForm;
use App\Form\TimesheetMultiUserEditForm;
@@ -45,19 +46,19 @@ class TimesheetTeamController extends TimesheetAbstractController
*/
public function indexAction(int $page, Request $request): Response
{
$query = new TimesheetQuery();
$query = $this->createDefaultQuery();
$query->setPage($page);
$query->setName('TeamTimesListing');
return $this->index($query, $request, 'admin_timesheet', 'timesheet-team/index.html.twig', TimesheetMetaDisplayEvent::TEAM_TIMESHEET);
}
/**
* @Route(path="/export/{exporter}", name="admin_timesheet_export", methods={"GET"})
* @Route(path="/export/", name="admin_timesheet_export", methods={"GET", "POST"})
* @Security("is_granted('export_other_timesheet')")
*/
public function exportAction(Request $request, string $exporter): Response
public function exportAction(Request $request, ServiceExport $serviceExport): Response
{
return $this->export($request, $exporter);
return $this->export($request, $serviceExport);
}
/**
@@ -233,6 +234,11 @@ class TimesheetTeamController extends TimesheetAbstractController
return 'admin_timesheet_edit';
}
protected function getExportRoute(): string
{
return 'admin_timesheet_export';
}
protected function getMultiUpdateRoute(): string
{
return 'admin_timesheet_multi_update';
@@ -247,4 +253,9 @@ class TimesheetTeamController extends TimesheetAbstractController
{
return true;
}
protected function getQueryNamePrefix(): string
{
return 'TeamTimes';
}
}

View File

@@ -1,39 +0,0 @@
<?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\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Export\ServiceExport;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
abstract class AbstractTimesheetsSubscriber extends AbstractActionsSubscriber
{
private $serviceExport;
public function __construct(AuthorizationCheckerInterface $security, UrlGeneratorInterface $urlGenerator, ServiceExport $serviceExport)
{
parent::__construct($security, $urlGenerator);
$this->serviceExport = $serviceExport;
}
protected function addExporter(PageActionsEvent $event, string $routeExport): void
{
$allExporter = $this->serviceExport->getTimesheetExporter();
if (\count($allExporter) === 1) {
$event->addAction('download', ['url' => $this->path($routeExport, ['exporter' => $allExporter[0]->getId()]), 'class' => 'toolbar-action']);
} else {
foreach ($allExporter as $exporter) {
$id = $exporter->getId();
$event->addActionToSubmenu('download', 'exporter.' . $id, ['title' => 'button.' . $id, 'url' => $this->path($routeExport, ['exporter' => $id]), 'class' => 'toolbar-action exporter-' . $id]);
}
}
}
}

View File

@@ -39,7 +39,7 @@ class ReportingSubscriber extends AbstractActionsSubscriber
if ($report instanceof Report) {
$subMenu = $report->getReportIcon();
}
$event->addActionToSubmenu($subMenu, $report->getId(), ['title' => $report->getLabel(), 'translation_domain' => 'reporting', 'url' => $this->path($report->getRoute()), 'class' => 'toolbar-action report-' . $report->getId()]);
$event->addActionToSubmenu($subMenu, $report->getId(), ['title' => $report->getLabel(), 'translation_domain' => 'reporting', 'url' => $this->path($report->getRoute()), 'class' => 'report-' . $report->getId()]);
}
$event->addHelp($this->documentationLink('reporting.html'));

View File

@@ -11,7 +11,7 @@ namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
class TimesheetsSubscriber extends AbstractTimesheetsSubscriber
class TimesheetsSubscriber extends AbstractActionsSubscriber
{
public static function getActionName(): string
{
@@ -24,7 +24,7 @@ class TimesheetsSubscriber extends AbstractTimesheetsSubscriber
$event->addColumnToggle('#modal_timesheet');
if ($this->isGranted('export_own_timesheet')) {
$this->addExporter($event, 'timesheet_export');
$event->addAction('download', ['url' => $this->path('timesheet_export'), 'class' => 'toolbar-action modal-ajax-form']);
}
if ($this->isGranted('create_own_timesheet')) {

View File

@@ -11,7 +11,7 @@ namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
class TimesheetsTeamSubscriber extends AbstractTimesheetsSubscriber
class TimesheetsTeamSubscriber extends AbstractActionsSubscriber
{
public static function getActionName(): string
{
@@ -24,7 +24,7 @@ class TimesheetsTeamSubscriber extends AbstractTimesheetsSubscriber
$event->addColumnToggle('#modal_timesheet_admin');
if ($this->isGranted('export_other_timesheet')) {
$this->addExporter($event, 'admin_timesheet_export');
$event->addAction('download', ['url' => $this->path('admin_timesheet_export'), 'class' => 'toolbar-action modal-ajax-form']);
}
if ($this->isGranted('create_other_timesheet')) {

View File

@@ -706,36 +706,43 @@ abstract class AbstractSpreadsheetRenderer
$entryHeaderRow++;
}
if (null !== $durationColumn) {
$startCoordinate = $sheet->getCellByColumnAndRow($durationColumn, 2)->getCoordinate();
$endCoordinate = $sheet->getCellByColumnAndRow($durationColumn, $entryHeaderRow - 1)->getCoordinate();
$this->setDurationTotal($sheet, $durationColumn, $entryHeaderRow, $startCoordinate, $endCoordinate);
$style = $sheet->getStyleByColumnAndRow($durationColumn, $entryHeaderRow);
$style->getBorders()->getTop()->setBorderStyle(Border::BORDER_THIN);
$style->getFont()->setBold(true);
}
if ($this->isTotalRowSupported()) {
if (null !== $durationColumn) {
$startCoordinate = $sheet->getCellByColumnAndRow($durationColumn, 2)->getCoordinate();
$endCoordinate = $sheet->getCellByColumnAndRow($durationColumn, $entryHeaderRow - 1)->getCoordinate();
$this->setDurationTotal($sheet, $durationColumn, $entryHeaderRow, $startCoordinate, $endCoordinate);
$style = $sheet->getStyleByColumnAndRow($durationColumn, $entryHeaderRow);
$style->getBorders()->getTop()->setBorderStyle(Border::BORDER_THIN);
$style->getFont()->setBold(true);
}
if (null !== $rateColumn) {
$startCoordinate = $sheet->getCellByColumnAndRow($rateColumn, 2)->getCoordinate();
$endCoordinate = $sheet->getCellByColumnAndRow($rateColumn, $entryHeaderRow - 1)->getCoordinate();
$this->setRateTotal($sheet, $rateColumn, $entryHeaderRow, $startCoordinate, $endCoordinate);
$style = $sheet->getStyleByColumnAndRow($rateColumn, $entryHeaderRow);
$style->getBorders()->getTop()->setBorderStyle(Border::BORDER_THIN);
$style->getFont()->setBold(true);
}
if (null !== $rateColumn) {
$startCoordinate = $sheet->getCellByColumnAndRow($rateColumn, 2)->getCoordinate();
$endCoordinate = $sheet->getCellByColumnAndRow($rateColumn, $entryHeaderRow - 1)->getCoordinate();
$this->setRateTotal($sheet, $rateColumn, $entryHeaderRow, $startCoordinate, $endCoordinate);
$style = $sheet->getStyleByColumnAndRow($rateColumn, $entryHeaderRow);
$style->getBorders()->getTop()->setBorderStyle(Border::BORDER_THIN);
$style->getFont()->setBold(true);
}
if (null !== $internalRateColumn) {
$startCoordinate = $sheet->getCellByColumnAndRow($internalRateColumn, 2)->getCoordinate();
$endCoordinate = $sheet->getCellByColumnAndRow($internalRateColumn, $entryHeaderRow - 1)->getCoordinate();
$this->setRateTotal($sheet, $internalRateColumn, $entryHeaderRow, $startCoordinate, $endCoordinate);
$style = $sheet->getStyleByColumnAndRow($internalRateColumn, $entryHeaderRow);
$style->getBorders()->getTop()->setBorderStyle(Border::BORDER_THIN);
$style->getFont()->setBold(true);
if (null !== $internalRateColumn) {
$startCoordinate = $sheet->getCellByColumnAndRow($internalRateColumn, 2)->getCoordinate();
$endCoordinate = $sheet->getCellByColumnAndRow($internalRateColumn, $entryHeaderRow - 1)->getCoordinate();
$this->setRateTotal($sheet, $internalRateColumn, $entryHeaderRow, $startCoordinate, $endCoordinate);
$style = $sheet->getStyleByColumnAndRow($internalRateColumn, $entryHeaderRow);
$style->getBorders()->getTop()->setBorderStyle(Border::BORDER_THIN);
$style->getFont()->setBold(true);
}
}
return $spreadsheet;
}
protected function isTotalRowSupported(): bool
{
return false;
}
/**
* @param ExportItemInterface[] $exportItems
* @param TimesheetQuery $query

View File

@@ -167,7 +167,9 @@ trait RendererTrait
}
}
$allBudgets = $projectStatisticService->getBudgetStatisticModelForProjects($projects, $query->getEnd());
$today = $this->getToday($query);
$allBudgets = $projectStatisticService->getBudgetStatisticModelForProjects($projects, $today);
foreach ($allBudgets as $projectId => $statisticModel) {
$project = $statisticModel->getProject();
@@ -183,6 +185,29 @@ trait RendererTrait
return $summary;
}
private function getToday(TimesheetQuery $query): \DateTime
{
$end = $query->getEnd();
if ($end !== null) {
return $end;
}
if ($query->getCurrentUser() !== null) {
$timezone = $query->getCurrentUser()->getTimezone();
return new \DateTime('now', new \DateTimeZone($timezone));
}
if ($query->getUser() !== null) {
$timezone = $query->getUser()->getTimezone();
return new \DateTime('now', new \DateTimeZone($timezone));
}
return new \DateTime();
}
/**
* @param ExportItemInterface[] $exportItems
* @param TimesheetQuery $query
@@ -236,7 +261,9 @@ trait RendererTrait
}
}
$allBudgets = $activityStatisticService->getBudgetStatisticModelForActivities($activities, $query->getEnd());
$today = $this->getToday($query);
$allBudgets = $activityStatisticService->getBudgetStatisticModelForActivities($activities, $today);
foreach ($allBudgets as $activityId => $statisticModel) {
$project = $statisticModel->getActivity()->getProject();

View File

@@ -15,6 +15,11 @@ use PhpOffice\PhpSpreadsheet\Style\Alignment;
class XlsxRenderer extends AbstractSpreadsheetRenderer
{
protected function isTotalRowSupported(): bool
{
return true;
}
public function getFileExtension(): string
{
return '.xlsx';

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Form\Toolbar;
use App\Repository\Query\TimesheetQuery;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Defines the form used for filtering the timesheet.
*/
class TimesheetExportToolbarForm extends AbstractToolbarForm
{
public function getBlockPrefix()
{
return 'export';
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$newOptions = [];
if ($options['ignore_date'] === true) {
$newOptions['ignore_date'] = true;
}
$this->addSearchTermInputField($builder);
$this->addDateRange($builder, ['timezone' => $options['timezone']]);
$this->addCustomerMultiChoice($builder, $newOptions, true);
$this->addProjectMultiChoice($builder, $newOptions, true, true);
$this->addActivityMultiChoice($builder, [], true);
$this->addTagInputField($builder);
if ($options['include_user']) {
$this->addUsersChoice($builder);
}
$this->addTimesheetStateChoice($builder);
$this->addBillableChoice($builder);
$this->addExportStateChoice($builder);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => TimesheetQuery::class,
'csrf_protection' => false,
'include_user' => false,
'ignore_date' => true,
'timezone' => date_default_timezone_get(),
]);
}
}

View File

@@ -0,0 +1,56 @@
<?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\Result;
use App\Repository\Loader\TimesheetLoader;
use Doctrine\ORM\QueryBuilder;
class TimesheetResult
{
private $queryBuilder;
public function __construct(QueryBuilder $queryBuilder)
{
$this->queryBuilder = $queryBuilder;
}
public function getStatistic(): TimesheetResultStatistic
{
$qb = clone $this->queryBuilder;
$qb
->resetDQLPart('select')
->resetDQLPart('orderBy')
->select('COUNT(t.id) as counter')
->addSelect('COALESCE(SUM(t.duration), 0) as duration')
;
$result = $qb->getQuery()->getArrayResult()[0];
return new TimesheetResultStatistic($result['counter'], $result['duration']);
}
public function toIterable(): iterable
{
$query = $this->queryBuilder->getQuery();
return $query->toIterable();
}
public function getResults(bool $fullyHydrated = false): array
{
$query = $this->queryBuilder->getQuery();
$results = $query->getResult();
$loader = new TimesheetLoader($this->queryBuilder->getEntityManager(), $fullyHydrated);
$loader->loadResults($results);
return $results;
}
}

View File

@@ -0,0 +1,32 @@
<?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\Result;
class TimesheetResultStatistic
{
private $count = 0;
private $duration = 0;
public function __construct(int $count, int $duration)
{
$this->count = $count;
$this->duration = $duration;
}
public function getCount(): int
{
return $this->count;
}
public function getDuration(): int
{
return $this->duration;
}
}

View File

@@ -25,6 +25,7 @@ use App\Repository\Loader\TimesheetLoader;
use App\Repository\Paginator\LoaderPaginator;
use App\Repository\Paginator\PaginatorInterface;
use App\Repository\Query\TimesheetQuery;
use App\Repository\Result\TimesheetResult;
use DateInterval;
use DateTime;
use Doctrine\DBAL\Types\Types;
@@ -749,7 +750,7 @@ class TimesheetRepository extends EntityRepository
* Especially the following question is still un-answered!
*
* Should a teamlead:
* 1 . see all records of his team-members, even if they recorded times for projects invisible to him
* 1. see all records of his team-members, even if they recorded times for projects invisible to him
* 2. only see records for projects which can be accessed by hom (current situation)
*/
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = []): bool
@@ -835,6 +836,13 @@ class TimesheetRepository extends EntityRepository
return $this->getHydratedResultsByQuery($qb, $fullyHydrated);
}
public function getTimesheetResult(TimesheetQuery $query): TimesheetResult
{
$qb = $this->getQueryBuilderForQuery($query);
return new TimesheetResult($qb);
}
/**
* @param QueryBuilder $qb
* @param bool $fullyHydrated