support different formats in user timesheet exports (#1222)
This commit is contained in:
@@ -9,361 +9,11 @@
|
||||
|
||||
namespace App\Export\Renderer;
|
||||
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Event\ActivityMetaDisplayEvent;
|
||||
use App\Event\CustomerMetaDisplayEvent;
|
||||
use App\Event\MetaDisplayEventInterface;
|
||||
use App\Event\ProjectMetaDisplayEvent;
|
||||
use App\Event\TimesheetMetaDisplayEvent;
|
||||
use App\Event\UserPreferenceDisplayEvent;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Twig\DateExtensions;
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use App\Export\Base\AbstractSpreadsheetRenderer as BaseAbstractSpreadsheetRenderer;
|
||||
|
||||
abstract class AbstractSpreadsheetRenderer
|
||||
/**
|
||||
* @deprecated since 1.6, will be removed with 2.0
|
||||
*/
|
||||
abstract class AbstractSpreadsheetRenderer extends BaseAbstractSpreadsheetRenderer
|
||||
{
|
||||
public const DATETIME_FORMAT = 'yyyy-mm-dd hh:mm';
|
||||
public const TIME_FORMAT = 'hh:mm';
|
||||
public const DURATION_FORMAT = '[hh]:mm';
|
||||
public const RATE_FORMAT_DEFAULT = '#.##0,00 [$%1$s];-#.##0,00 [$%1$s]';
|
||||
public const RATE_FORMAT_LEFT = '_("%1$s"* #,##0.00_);_("%1$s"* \(#,##0.00\);_("%1$s"* "-"??_);_(@_)';
|
||||
public const RATE_FORMAT = self::RATE_FORMAT_LEFT;
|
||||
|
||||
/**
|
||||
* @var DateExtensions
|
||||
*/
|
||||
protected $dateExtension;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
public function __construct(TranslatorInterface $translator, DateExtensions $dateExtension, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->dateExtension = $dateExtension;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
protected function setFormattedDateTime(Worksheet $sheet, $column, $row, ?DateTime $date)
|
||||
{
|
||||
if (null === $date) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, '');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, Date::PHPToExcel($date));
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(self::DATETIME_FORMAT);
|
||||
}
|
||||
|
||||
protected function setFormattedTime(Worksheet $sheet, $column, $row, ?DateTime $date)
|
||||
{
|
||||
if (null === $date) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, '');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, Date::PHPToExcel($date));
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(self::TIME_FORMAT);
|
||||
}
|
||||
|
||||
protected function setFormattedDate(Worksheet $sheet, $column, $row, ?DateTime $date)
|
||||
{
|
||||
if (null === $date) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, '');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, Date::PHPToExcel($date));
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2);
|
||||
}
|
||||
|
||||
protected function setDurationTotal(Worksheet $sheet, $column, $row, $startCoordinate, $endCoordinate)
|
||||
{
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, sprintf('=SUM(%s:%s)', $startCoordinate, $endCoordinate));
|
||||
$style = $sheet->getStyleByColumnAndRow($column, $row);
|
||||
$style->getNumberFormat()->setFormatCode(self::DURATION_FORMAT);
|
||||
}
|
||||
|
||||
protected function setDuration(Worksheet $sheet, $column, $row, $duration)
|
||||
{
|
||||
if (null === $duration) {
|
||||
$duration = 0;
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, sprintf('=%s/86400', $duration));
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(self::DURATION_FORMAT);
|
||||
}
|
||||
|
||||
protected function setRateTotal(Worksheet $sheet, $column, $row, $startCoordinate, $endCoordinate)
|
||||
{
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, sprintf('=SUM(%s:%s)', $startCoordinate, $endCoordinate));
|
||||
}
|
||||
|
||||
protected function setRate(Worksheet $sheet, $column, $row, $rate, $currency)
|
||||
{
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $rate);
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(
|
||||
sprintf(self::RATE_FORMAT_LEFT, $currency)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MetaDisplayEventInterface $event
|
||||
* @return MetaTableTypeInterface[]
|
||||
*/
|
||||
protected function findMetaColumns(MetaDisplayEventInterface $event): array
|
||||
{
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
return $event->getFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param TimesheetQuery $query
|
||||
* @return Spreadsheet
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||
*/
|
||||
protected function fromArrayToSpreadsheet(array $timesheets, TimesheetQuery $query): Spreadsheet
|
||||
{
|
||||
$timesheetMetaFields = $this->findMetaColumns(new TimesheetMetaDisplayEvent($query, TimesheetMetaDisplayEvent::EXPORT));
|
||||
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($query, CustomerMetaDisplayEvent::EXPORT));
|
||||
$projectMetaFields = $this->findMetaColumns(new ProjectMetaDisplayEvent($query, ProjectMetaDisplayEvent::EXPORT));
|
||||
$activityMetaFields = $this->findMetaColumns(new ActivityMetaDisplayEvent($query, ActivityMetaDisplayEvent::EXPORT));
|
||||
|
||||
$event = new UserPreferenceDisplayEvent(UserPreferenceDisplayEvent::EXPORT);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$userPreferences = $event->getPreferences();
|
||||
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
$recordsHeaderColumn = 1;
|
||||
$recordsHeaderRow = 1;
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.date'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.begin'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.end'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.duration'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.rate'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.user'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.customer'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.project'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.activity'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.description'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.exported'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.tags'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.hourlyRate'));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.fixedRate'));
|
||||
foreach ($timesheetMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
foreach ($customerMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
foreach ($projectMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
foreach ($activityMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
foreach ($userPreferences as $preference) {
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans($preference->getLabel()));
|
||||
}
|
||||
|
||||
$entryHeaderRow = $recordsHeaderRow + 1;
|
||||
|
||||
$durationColumn = null;
|
||||
$rateColumn = null;
|
||||
|
||||
foreach ($timesheets as $timesheet) {
|
||||
$entryHeaderColumn = 1;
|
||||
|
||||
$customerCurrency = $timesheet->getProject()->getCustomer()->getCurrency();
|
||||
$exported = $timesheet->isExported() ? 'entryState.exported' : 'entryState.not_exported';
|
||||
|
||||
$this->setFormattedDate($sheet, $entryHeaderColumn, $entryHeaderRow, $timesheet->getBegin());
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$this->setFormattedTime($sheet, $entryHeaderColumn, $entryHeaderRow, $timesheet->getBegin());
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$this->setFormattedTime($sheet, $entryHeaderColumn, $entryHeaderRow, $timesheet->getEnd());
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$this->setDuration($sheet, $entryHeaderColumn, $entryHeaderRow, $timesheet->getDuration());
|
||||
$durationColumn = $entryHeaderColumn;
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$this->setRate($sheet, $entryHeaderColumn, $entryHeaderRow, $timesheet->getRate(), $customerCurrency);
|
||||
$rateColumn = $entryHeaderColumn;
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn, $entryHeaderRow, $this->getUsername($timesheet));
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn, $entryHeaderRow, $timesheet->getProject()->getCustomer()->getName());
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn, $entryHeaderRow, $timesheet->getProject()->getName());
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn, $entryHeaderRow, $timesheet->getActivity()->getName());
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn, $entryHeaderRow, $timesheet->getDescription());
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn, $entryHeaderRow, $this->translator->trans($exported));
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn, $entryHeaderRow, implode(',', $timesheet->getTagsAsArray()));
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$this->setRate($sheet, $entryHeaderColumn, $entryHeaderRow, $timesheet->getHourlyRate(), $customerCurrency);
|
||||
$entryHeaderColumn++;
|
||||
|
||||
$this->setRate($sheet, $entryHeaderColumn, $entryHeaderRow, $timesheet->getFixedRate(), $customerCurrency);
|
||||
$entryHeaderColumn++;
|
||||
|
||||
foreach ($timesheetMetaFields as $metaField) {
|
||||
$metaField = $timesheet->getMetaField($metaField->getName());
|
||||
$metaFieldValue = '';
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn++, $entryHeaderRow, $metaFieldValue);
|
||||
}
|
||||
foreach ($customerMetaFields as $metaField) {
|
||||
$metaField = $timesheet->getProject()->getCustomer()->getMetaField($metaField->getName());
|
||||
$metaFieldValue = '';
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn++, $entryHeaderRow, $metaFieldValue);
|
||||
}
|
||||
foreach ($projectMetaFields as $metaField) {
|
||||
$metaField = $timesheet->getProject()->getMetaField($metaField->getName());
|
||||
$metaFieldValue = '';
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn++, $entryHeaderRow, $metaFieldValue);
|
||||
}
|
||||
foreach ($activityMetaFields as $metaField) {
|
||||
$metaField = $timesheet->getActivity()->getMetaField($metaField->getName());
|
||||
$metaFieldValue = '';
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn++, $entryHeaderRow, $metaFieldValue);
|
||||
}
|
||||
foreach ($userPreferences as $preference) {
|
||||
$metaField = $timesheet->getUser()->getPreference($preference->getName());
|
||||
$metaFieldValue = '';
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn++, $entryHeaderRow, $metaFieldValue);
|
||||
}
|
||||
|
||||
$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 (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);
|
||||
}
|
||||
|
||||
return $spreadsheet;
|
||||
}
|
||||
|
||||
protected function getUsername(Timesheet $timesheet): string
|
||||
{
|
||||
if (!empty($timesheet->getUser()->getAlias())) {
|
||||
return $timesheet->getUser()->getAlias();
|
||||
}
|
||||
|
||||
return $timesheet->getUser()->getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
|
||||
*/
|
||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||
{
|
||||
$spreadsheet = $this->fromArrayToSpreadsheet($timesheets, $query);
|
||||
$filename = $this->saveSpreadsheet($spreadsheet);
|
||||
|
||||
return $this->getFileResponse($filename, 'kimai-export' . $this->getFileExtension());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getFileExtension(): string;
|
||||
|
||||
/**
|
||||
* @param mixed $file
|
||||
* @param string $filename
|
||||
* @return BinaryFileResponse
|
||||
*/
|
||||
protected function getFileResponse($file, $filename): Response
|
||||
{
|
||||
$response = new BinaryFileResponse($file);
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename);
|
||||
|
||||
$response->headers->set('Content-Type', $this->getContentType());
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
$response->deleteFileAfterSend(true);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getContentType(): string;
|
||||
|
||||
/**
|
||||
* @param Spreadsheet $spreadsheet
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
abstract protected function saveSpreadsheet(Spreadsheet $spreadsheet): string;
|
||||
}
|
||||
|
||||
@@ -9,73 +9,18 @@
|
||||
|
||||
namespace App\Export\Renderer;
|
||||
|
||||
use App\Export\Base\CsvRenderer as BaseCsvRenderer;
|
||||
use App\Export\RendererInterface;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
final class CsvRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
final class CsvRenderer extends BaseCsvRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return '.csv';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getContentType(): string
|
||||
{
|
||||
return 'text/csv';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Spreadsheet $spreadsheet
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function saveSpreadsheet(Spreadsheet $spreadsheet): string
|
||||
{
|
||||
$filename = tempnam(sys_get_temp_dir(), 'kimai-export-csv');
|
||||
if (false === $filename) {
|
||||
throw new \Exception('Could not open temporary file');
|
||||
}
|
||||
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Csv');
|
||||
$writer->save($filename);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'csv';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'csv';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'csv';
|
||||
}
|
||||
|
||||
protected function setDuration(Worksheet $sheet, $column, $row, $duration)
|
||||
{
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, sprintf('=%s', $duration));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,105 +9,16 @@
|
||||
|
||||
namespace App\Export\Renderer;
|
||||
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Event\ActivityMetaDisplayEvent;
|
||||
use App\Event\CustomerMetaDisplayEvent;
|
||||
use App\Event\MetaDisplayEventInterface;
|
||||
use App\Event\ProjectMetaDisplayEvent;
|
||||
use App\Event\TimesheetMetaDisplayEvent;
|
||||
use App\Event\UserPreferenceDisplayEvent;
|
||||
use App\Export\Base\HtmlRenderer as BaseHtmlRenderer;
|
||||
use App\Export\RendererInterface;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Twig\Environment;
|
||||
|
||||
final class HtmlRenderer implements RendererInterface
|
||||
final class HtmlRenderer extends BaseHtmlRenderer implements RendererInterface
|
||||
{
|
||||
use RendererTrait;
|
||||
|
||||
/**
|
||||
* @var Environment
|
||||
*/
|
||||
protected $twig;
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
public function __construct(Environment $twig, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MetaDisplayEventInterface $event
|
||||
* @return MetaTableTypeInterface[]
|
||||
*/
|
||||
protected function findMetaColumns(MetaDisplayEventInterface $event): array
|
||||
{
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
return $event->getFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
*/
|
||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||
{
|
||||
$timesheetMetaFields = $this->findMetaColumns(new TimesheetMetaDisplayEvent($query, TimesheetMetaDisplayEvent::EXPORT));
|
||||
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($query, CustomerMetaDisplayEvent::EXPORT));
|
||||
$projectMetaFields = $this->findMetaColumns(new ProjectMetaDisplayEvent($query, ProjectMetaDisplayEvent::EXPORT));
|
||||
$activityMetaFields = $this->findMetaColumns(new ActivityMetaDisplayEvent($query, ActivityMetaDisplayEvent::EXPORT));
|
||||
|
||||
$event = new UserPreferenceDisplayEvent(UserPreferenceDisplayEvent::EXPORT);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$userPreferences = $event->getPreferences();
|
||||
|
||||
$content = $this->twig->render('export/renderer/default.html.twig', [
|
||||
'entries' => $timesheets,
|
||||
'query' => $query,
|
||||
'summaries' => $this->calculateSummary($timesheets),
|
||||
'timesheetMetaFields' => $timesheetMetaFields,
|
||||
'customerMetaFields' => $customerMetaFields,
|
||||
'projectMetaFields' => $projectMetaFields,
|
||||
'activityMetaFields' => $activityMetaFields,
|
||||
'userPreferences' => $userPreferences,
|
||||
]);
|
||||
|
||||
$response = new Response();
|
||||
$response->setContent($content);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'html';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'print';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'print';
|
||||
|
||||
@@ -9,92 +9,16 @@
|
||||
|
||||
namespace App\Export\Renderer;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Export\Base\PDFRenderer as BasePDFRenderer;
|
||||
use App\Export\RendererInterface;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Timesheet\UserDateTimeFactory;
|
||||
use App\Utils\HtmlToPdfConverter;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
use Twig\Environment;
|
||||
|
||||
final class PDFRenderer implements RendererInterface
|
||||
final class PDFRenderer extends BasePDFRenderer implements RendererInterface
|
||||
{
|
||||
use RendererTrait;
|
||||
|
||||
/**
|
||||
* @var Environment
|
||||
*/
|
||||
protected $twig;
|
||||
/**
|
||||
* @var UserDateTimeFactory
|
||||
*/
|
||||
protected $dateTime;
|
||||
/**
|
||||
* @var HtmlToPdfConverter
|
||||
*/
|
||||
protected $converter;
|
||||
|
||||
/**
|
||||
* @param Environment $twig
|
||||
* @param UserDateTimeFactory $dateTime
|
||||
* @param HtmlToPdfConverter $converter
|
||||
*/
|
||||
public function __construct(Environment $twig, UserDateTimeFactory $dateTime, HtmlToPdfConverter $converter)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->dateTime = $dateTime;
|
||||
$this->converter = $converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
*/
|
||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||
{
|
||||
$content = $this->twig->render('export/renderer/pdf.html.twig', [
|
||||
'entries' => $timesheets,
|
||||
'query' => $query,
|
||||
'now' => $this->dateTime->createDateTime(),
|
||||
'summaries' => $this->calculateSummary($timesheets),
|
||||
]);
|
||||
|
||||
$content = $this->converter->convertToPdf($content);
|
||||
|
||||
$response = new Response($content);
|
||||
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'kimai-export.pdf');
|
||||
|
||||
$response->headers->set('Content-Type', 'application/pdf');
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'pdf';
|
||||
|
||||
@@ -9,55 +9,12 @@
|
||||
|
||||
namespace App\Export\Renderer;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Export\Base\RendererTrait as BaseRendererTrait;
|
||||
|
||||
/**
|
||||
* @deprecated since 1.6, will be removed with 2.0
|
||||
*/
|
||||
trait RendererTrait
|
||||
{
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @return array
|
||||
*/
|
||||
protected function calculateSummary(array $timesheets)
|
||||
{
|
||||
$summary = [];
|
||||
|
||||
foreach ($timesheets as $timesheet) {
|
||||
$id = $timesheet->getProject()->getCustomer()->getId() . '_' . $timesheet->getProject()->getId();
|
||||
$activityId = $timesheet->getActivity()->getId();
|
||||
|
||||
if (!isset($summary[$id])) {
|
||||
$summary[$id] = [
|
||||
'customer' => $timesheet->getProject()->getCustomer()->getName(),
|
||||
'project' => $timesheet->getProject()->getName(),
|
||||
'activities' => [],
|
||||
'currency' => $timesheet->getProject()->getCustomer()->getCurrency(),
|
||||
'rate' => 0,
|
||||
'duration' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($summary[$id]['activities'][$activityId])) {
|
||||
$summary[$id]['activities'][$activityId] = [
|
||||
'activity' => $timesheet->getActivity()->getName(),
|
||||
'currency' => $timesheet->getProject()->getCustomer()->getCurrency(),
|
||||
'rate' => 0,
|
||||
'duration' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$duration = $timesheet->getDuration();
|
||||
if (null === $duration) {
|
||||
$duration = 0;
|
||||
}
|
||||
|
||||
$summary[$id]['rate'] += $timesheet->getRate();
|
||||
$summary[$id]['duration'] += $duration;
|
||||
$summary[$id]['activities'][$activityId]['rate'] += $timesheet->getRate();
|
||||
$summary[$id]['activities'][$activityId]['duration'] += $duration;
|
||||
}
|
||||
|
||||
asort($summary);
|
||||
|
||||
return $summary;
|
||||
}
|
||||
use BaseRendererTrait;
|
||||
}
|
||||
|
||||
@@ -9,65 +9,16 @@
|
||||
|
||||
namespace App\Export\Renderer;
|
||||
|
||||
use App\Export\Base\XlsxRenderer as BaseXlsxRenderer;
|
||||
use App\Export\RendererInterface;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
final class XlsxRenderer extends AbstractSpreadsheetRenderer implements RendererInterface
|
||||
final class XlsxRenderer extends BaseXlsxRenderer implements RendererInterface
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return '.xlsx';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getContentType(): string
|
||||
{
|
||||
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Spreadsheet $spreadsheet
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function saveSpreadsheet(Spreadsheet $spreadsheet): string
|
||||
{
|
||||
$filename = tempnam(sys_get_temp_dir(), 'kimai-export-xlsx');
|
||||
if (false === $filename) {
|
||||
throw new \Exception('Could not open temporary file');
|
||||
}
|
||||
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
|
||||
$writer->save($filename);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return 'xlsx';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIcon(): string
|
||||
{
|
||||
return 'xlsx';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'xlsx';
|
||||
|
||||
Reference in New Issue
Block a user