allow custom export repositories (#2182)

This commit is contained in:
Kevin Papst
2020-12-10 15:00:14 +01:00
committed by GitHub
parent c3fe8d1c39
commit 7a3388646b
18 changed files with 320 additions and 72 deletions

View File

@@ -13,8 +13,6 @@ use App\Entity\Timesheet;
use App\Export\ServiceExport;
use App\Form\Toolbar\ExportToolbarForm;
use App\Repository\Query\ExportQuery;
use App\Repository\TimesheetRepository;
use App\Timesheet\UserDateTimeFactory;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\SubmitButton;
@@ -30,29 +28,14 @@ use Symfony\Component\Routing\Annotation\Route;
*/
class ExportController extends AbstractController
{
/**
* @var TimesheetRepository
*/
protected $timesheetRepository;
/**
* @var ServiceExport
*/
protected $export;
/**
* @var UserDateTimeFactory
*/
protected $dateFactory;
private $export;
/**
* @param TimesheetRepository $timesheet
* @param ServiceExport $export
*/
public function __construct(TimesheetRepository $timesheet, ServiceExport $export, UserDateTimeFactory $dateTime)
public function __construct(ServiceExport $export)
{
$this->timesheetRepository = $timesheet;
$this->export = $export;
$this->dateFactory = $dateTime;
}
/**
@@ -100,7 +83,7 @@ class ExportController extends AbstractController
$form = $this->getToolbarForm($query, 'POST');
$form->handleRequest($request);
$type = $query->getType();
$type = $query->getRenderer();
if (null === $type) {
throw $this->createNotFoundException('Missing export renderer');
}
@@ -114,8 +97,9 @@ class ExportController extends AbstractController
$entries = $this->getEntries($query);
$response = $renderer->render($entries, $query);
// TODO check entries if user is allowed to update export state - see https://github.com/kevinpapst/kimai2/issues/1473
if ($query->isMarkAsExported()) {
$this->timesheetRepository->setExported($entries);
$this->export->setExported($entries);
}
return $response;
@@ -123,8 +107,8 @@ class ExportController extends AbstractController
protected function getDefaultQuery(): ExportQuery
{
$begin = $this->dateFactory->createDateTime('first day of this month 00:00:00');
$end = $this->dateFactory->createDateTime('last day of this month 23:59:59');
$begin = $this->getDateTimeFactory()->getStartOfMonth();
$end = $this->getDateTimeFactory()->getEndOfMonth();
$query = new ExportQuery();
$query->setOrder(ExportQuery::ORDER_ASC);
@@ -150,13 +134,14 @@ class ExportController extends AbstractController
$query->getEnd()->setTime(23, 59, 59);
}
return $this->timesheetRepository->getTimesheetsForQuery($query, true);
return $this->export->getExportItems($query);
}
protected function getToolbarForm(ExportQuery $query, string $method): FormInterface
{
return $this->createForm(ExportToolbarForm::class, $query, [
'action' => $this->generateUrl('export', []),
'include_user' => $this->isGranted('view_other_timesheet'),
'method' => $method,
'attr' => [
'id' => 'export-form'

View File

@@ -42,6 +42,11 @@ class ExportServiceCompilerPass implements CompilerPassInterface
$definition->addMethodCall('addTimesheetExporter', [new Reference($id)]);
}
$taggedRepository = $container->findTaggedServiceIds(Kernel::TAG_EXPORT_REPOSITORY);
foreach ($taggedRepository as $id => $tags) {
$definition->addMethodCall('addExportRepository', [new Reference($id)]);
}
$path = \dirname(\dirname(\dirname(__DIR__))) . DIRECTORY_SEPARATOR;
foreach ($container->getParameter('kimai.export.documents') as $exportPath) {
if (!is_dir($path . $exportPath)) {

View File

@@ -46,6 +46,8 @@ trait RendererTrait
}
$id = $customerId . '_' . $projectId;
$type = $exportItem->getType();
$category = $exportItem->getCategory();
if (!isset($summary[$id])) {
$summary[$id] = [
@@ -56,6 +58,24 @@ trait RendererTrait
'rate' => 0,
'rate_internal' => 0,
'duration' => 0,
'type' => [],
'types' => [],
];
}
if (!isset($summary[$id]['type'][$type])) {
$summary[$id]['type'][$type] = [
'rate' => 0,
'rate_internal' => 0,
'duration' => 0,
];
}
if (!isset($summary[$id]['types'][$type][$category])) {
$summary[$id]['types'][$type][$category] = [
'rate' => 0,
'rate_internal' => 0,
'duration' => 0,
];
}
@@ -75,12 +95,23 @@ trait RendererTrait
}
$summary[$id]['rate'] += $exportItem->getRate();
$summary[$id]['type'][$type]['rate'] += $exportItem->getRate();
$summary[$id]['types'][$type][$category]['rate'] += $exportItem->getRate();
if (method_exists($exportItem, 'getInternalRate')) {
$summary[$id]['rate_internal'] += $exportItem->getInternalRate();
$summary[$id]['type'][$type]['rate_internal'] += $exportItem->getInternalRate();
$summary[$id]['types'][$type][$category]['rate_internal'] += $exportItem->getInternalRate();
} else {
$summary[$id]['rate_internal'] += $exportItem->getRate();
$summary[$id]['type'][$type]['rate_internal'] += $exportItem->getRate();
$summary[$id]['types'][$type][$category]['rate_internal'] += $exportItem->getRate();
}
$summary[$id]['duration'] += $duration;
$summary[$id]['type'][$type]['duration'] += $duration;
$summary[$id]['types'][$type][$category]['duration'] += $duration;
$summary[$id]['activities'][$activityId]['rate'] += $exportItem->getRate();
$summary[$id]['activities'][$activityId]['duration'] += $duration;
}

View File

@@ -0,0 +1,38 @@
<?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\Export;
use App\Repository\Query\ExportQuery;
interface ExportRepositoryInterface
{
/**
* This method will receive ALL exported items, loaded from all repositories.
* Be careful to only handle the ones, which belong to your repository.
*
* @param ExportItemInterface[] $items
* @return void
*/
public function setExported(array $items): void;
/**
* @param ExportQuery $query
* @return ExportItemInterface[]
*/
public function getExportItemsForQuery(ExportQuery $query): iterable;
/**
* Returns the type of this repository.
* Must match the value returned by your entities via ExportItemInterface::getType().
*
* @return string
*/
public function getType(): string;
}

View File

@@ -9,17 +9,22 @@
namespace App\Export;
use App\Repository\Query\ExportQuery;
final class ServiceExport
{
/**
* @var ExportRendererInterface[]
*/
private $renderer = [];
/**
* @var TimesheetExportInterface[]
*/
private $exporter = [];
/**
* @var ExportRepositoryInterface[]
*/
private $repositories = [];
public function addRenderer(ExportRendererInterface $renderer): ServiceExport
{
@@ -72,4 +77,29 @@ final class ServiceExport
return null;
}
public function addExportRepository(ExportRepositoryInterface $repository): ServiceExport
{
$this->repositories[] = $repository;
return $this;
}
public function getExportItems(ExportQuery $query)
{
$items = [];
foreach ($this->repositories as $repository) {
$items = array_merge($items, $repository->getExportItemsForQuery($query));
}
return $items;
}
public function setExported(array $items): void
{
foreach ($this->repositories as $repository) {
$repository->setExported($items);
}
}
}

View 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\Export;
use App\Entity\Timesheet;
use App\Repository\Query\ExportQuery;
use App\Repository\TimesheetRepository;
final class TimesheetExportRepository implements ExportRepositoryInterface
{
/**
* @var TimesheetRepository
*/
private $repository;
public function __construct(TimesheetRepository $repository)
{
$this->repository = $repository;
}
/**
* @param Timesheet[] $items
*/
public function setExported(array $items): void
{
$timesheets = [];
foreach ($items as $item) {
if ($item instanceof Timesheet) {
$timesheets[] = $item;
}
}
if (empty($timesheets)) {
return;
}
$this->repository->setExported($timesheets);
}
public function getExportItemsForQuery(ExportQuery $query): iterable
{
return $this->repository->getTimesheetsForQuery($query, true);
}
public function getType(): string
{
return 'timesheet';
}
}

View File

@@ -29,12 +29,14 @@ class ExportToolbarForm extends AbstractToolbarForm
$this->addSearchTermInputField($builder);
$this->addExportStateChoice($builder);
$this->addTimesheetStateChoice($builder);
$this->addUsersChoice($builder);
if ($options['include_user']) {
$this->addUsersChoice($builder);
}
$this->addDateRangeChoice($builder);
$this->addCustomerMultiChoice($builder, ['start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true], true);
$this->addProjectMultiChoice($builder, ['ignore_date' => true], true, true);
$this->addActivityMultiChoice($builder, [], true);
$this->addExportType($builder);
$this->addExportRenderer($builder);
$this->addTagInputField($builder);
$builder->add('markAsExported', CheckboxType::class, [
'label' => 'label.mark_as_exported',
@@ -48,9 +50,9 @@ class ExportToolbarForm extends AbstractToolbarForm
/**
* @param FormBuilderInterface $builder
*/
protected function addExportType(FormBuilderInterface $builder)
protected function addExportRenderer(FormBuilderInterface $builder)
{
$builder->add('type', HiddenType::class, []);
$builder->add('renderer', HiddenType::class, []);
}
/**
@@ -61,6 +63,7 @@ class ExportToolbarForm extends AbstractToolbarForm
$resolver->setDefaults([
'data_class' => ExportQuery::class,
'csrf_protection' => false,
'include_user' => true,
]);
}
}

View File

@@ -15,6 +15,7 @@ use App\DependencyInjection\Compiler\ExportServiceCompilerPass;
use App\DependencyInjection\Compiler\InvoiceServiceCompilerPass;
use App\DependencyInjection\Compiler\TwigContextCompilerPass;
use App\DependencyInjection\Compiler\WidgetCompilerPass;
use App\Export\ExportRepositoryInterface;
use App\Export\RendererInterface as ExportRendererInterface;
use App\Export\TimesheetExportInterface;
use App\Invoice\CalculatorInterface as InvoiceCalculator;
@@ -50,6 +51,7 @@ class Kernel extends BaseKernel
public const TAG_WIDGET = 'widget';
public const TAG_WIDGET_RENDERER = 'widget.renderer';
public const TAG_EXPORT_RENDERER = 'export.renderer';
public const TAG_EXPORT_REPOSITORY = 'export.repository';
public const TAG_INVOICE_RENDERER = 'invoice.renderer';
public const TAG_INVOICE_NUMBER_GENERATOR = 'invoice.number_generator';
public const TAG_INVOICE_CALCULATOR = 'invoice.calculator';
@@ -74,6 +76,7 @@ class Kernel extends BaseKernel
{
$container->registerForAutoconfiguration(TimesheetCalculator::class)->addTag(self::TAG_TIMESHEET_CALCULATOR);
$container->registerForAutoconfiguration(ExportRendererInterface::class)->addTag(self::TAG_EXPORT_RENDERER);
$container->registerForAutoconfiguration(ExportRepositoryInterface::class)->addTag(self::TAG_EXPORT_REPOSITORY);
$container->registerForAutoconfiguration(InvoiceRendererInterface::class)->addTag(self::TAG_INVOICE_RENDERER);
$container->registerForAutoconfiguration(NumberGeneratorInterface::class)->addTag(self::TAG_INVOICE_NUMBER_GENERATOR);
$container->registerForAutoconfiguration(InvoiceCalculator::class)->addTag(self::TAG_INVOICE_CALCULATOR);

View File

@@ -14,20 +14,20 @@ class ExportQuery extends TimesheetQuery
/**
* @var string
*/
private $type;
private $renderer;
/**
* @var bool
*/
private $markAsExported = false;
public function getType(): ?string
public function getRenderer(): ?string
{
return $this->type;
return $this->renderer;
}
public function setType(string $type): ExportQuery
public function setRenderer(string $renderer): ExportQuery
{
$this->type = $type;
$this->renderer = $renderer;
return $this;
}

View File

@@ -36,16 +36,22 @@ final class TimesheetInvoiceItemRepository implements InvoiceItemRepositoryInter
}
/**
* @param Timesheet[] $invoiceItems
* @param InvoiceItemInterface[] $invoiceItems
*/
public function setExported(array $invoiceItems)
{
$timesheets = [];
foreach ($invoiceItems as $item) {
if (!$item instanceof Timesheet) {
throw new \InvalidArgumentException('TimesheetInvoiceItemRepository only supports Timesheet entities');
if ($item instanceof Timesheet) {
$timesheets[] = $item;
}
}
$this->repository->setExported($invoiceItems);
if (empty($timesheets)) {
return;
}
$this->repository->setExported($timesheets);
}
}

View File

@@ -957,17 +957,21 @@ class TimesheetRepository extends EntityRepository
$em = $this->getEntityManager();
$em->beginTransaction();
$qb = $em->createQueryBuilder();
$qb
->update(Timesheet::class, 't')
->set('t.exported', ':exported')
->where($qb->expr()->in('t.id', ':ids'))
->setParameter('exported', true, PDO::PARAM_BOOL)
->setParameter('ids', $timesheets)
->getQuery()
->execute();
try {
$qb = $em->createQueryBuilder();
$qb
->update(Timesheet::class, 't')
->set('t.exported', ':exported')
->where($qb->expr()->in('t.id', ':ids'))
->setParameter('exported', true, PDO::PARAM_BOOL)
->setParameter('ids', $timesheets)
->getQuery()
->execute();
$em->commit();
$em->commit();
} catch (\Exception $ex) {
$em->rollback();
}
}
/**