allow custom export repositories (#2182)
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
38
src/Export/ExportRepositoryInterface.php
Normal file
38
src/Export/ExportRepositoryInterface.php
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
57
src/Export/TimesheetExportRepository.php
Normal file
57
src/Export/TimesheetExportRepository.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\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';
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user