re-written exporter for simpler extendability (#1349)
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
use App\Export\ExportItemInterface;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
@@ -39,8 +39,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
* columns={"start_time","end_time"} => IDX_4F60C6B1502DF58741561401 => ???
|
||||
* columns={"start_time","end_time","user"} => IDX_4F60C6B1502DF587415614018D93D649 => ???
|
||||
*/
|
||||
class Timesheet implements EntityWithMetaFields, InvoiceItemInterface
|
||||
class Timesheet implements EntityWithMetaFields, ExportItemInterface
|
||||
{
|
||||
public const TYPE_TIMESHEET = 'timesheet';
|
||||
public const CATEGORY_WORK = 'work';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
@@ -385,7 +388,7 @@ class Timesheet implements EntityWithMetaFields, InvoiceItemInterface
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTagsAsArray()
|
||||
public function getTagsAsArray(): array
|
||||
{
|
||||
return array_map(
|
||||
function (Tag $element) {
|
||||
@@ -437,6 +440,18 @@ class Timesheet implements EntityWithMetaFields, InvoiceItemInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
// this will be improved in a future version
|
||||
return self::TYPE_TIMESHEET;
|
||||
}
|
||||
|
||||
public function getCategory(): string
|
||||
{
|
||||
// this will be improved in a future version
|
||||
return self::CATEGORY_WORK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal only here for symfony forms
|
||||
* @return Collection|MetaTableTypeInterface[]
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
namespace App\Export\Base;
|
||||
|
||||
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\ExportItemInterface;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Twig\DateExtensions;
|
||||
@@ -61,6 +61,30 @@ abstract class AbstractSpreadsheetRenderer
|
||||
* @var AuthorizationCheckerInterface
|
||||
*/
|
||||
protected $voter;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $columns = [
|
||||
'date' => [],
|
||||
'begin' => [],
|
||||
'end' => [],
|
||||
'duration' => [],
|
||||
'rate' => [],
|
||||
'user' => [],
|
||||
'customer' => [],
|
||||
'project' => [],
|
||||
'activity' => [],
|
||||
'description' => [],
|
||||
'exported' => [],
|
||||
'tags' => [],
|
||||
'hourlyRate' => [],
|
||||
'fixedRate' => [],
|
||||
'timesheet-meta' => [],
|
||||
'customer-meta' => [],
|
||||
'project-meta' => [],
|
||||
'activity-meta' => [],
|
||||
'user-meta' => [],
|
||||
];
|
||||
|
||||
public function __construct(TranslatorInterface $translator, DateExtensions $dateExtension, EventDispatcherInterface $dispatcher, AuthorizationCheckerInterface $voter)
|
||||
{
|
||||
@@ -72,7 +96,11 @@ abstract class AbstractSpreadsheetRenderer
|
||||
|
||||
protected function isRenderRate(TimesheetQuery $query): bool
|
||||
{
|
||||
return true;
|
||||
if (null !== $query->getUser()) {
|
||||
return $this->voter->isGranted('view_rate_own_timesheet');
|
||||
}
|
||||
|
||||
return $this->voter->isGranted('view_rate_other_timesheet');
|
||||
}
|
||||
|
||||
protected function setFormattedDateTime(Worksheet $sheet, $column, $row, ?DateTime $date)
|
||||
@@ -152,75 +180,302 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @param array $columns
|
||||
* @return array
|
||||
*/
|
||||
protected function getColumns(array $exportItems, TimesheetQuery $query, array $columns): array
|
||||
{
|
||||
$showRates = $this->isRenderRate($query);
|
||||
|
||||
if (isset($columns['date']) && !isset($columns['date']['render'])) {
|
||||
$columns['date']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$this->setFormattedDate($sheet, $column, $row, $entity->getBegin());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['begin']) && !isset($columns['begin']['render'])) {
|
||||
$columns['begin']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$this->setFormattedTime($sheet, $column, $row, $entity->getBegin());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['end']) && !isset($columns['end']['render'])) {
|
||||
$columns['end']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$this->setFormattedTime($sheet, $column, $row, $entity->getEnd());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['duration']) && !isset($columns['duration']['render'])) {
|
||||
$columns['duration']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$this->setDuration($sheet, $column, $row, $entity->getDuration());
|
||||
};
|
||||
}
|
||||
|
||||
if ($showRates && isset($columns['rate']) && !isset($columns['rate']['render'])) {
|
||||
$columns['rate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$currency = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$currency = $entity->getProject()->getCustomer()->getCurrency();
|
||||
}
|
||||
$this->setRate($sheet, $column, $row, $entity->getRate(), $currency);
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['user']) && !isset($columns['user']['render'])) {
|
||||
$columns['user']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$user = '';
|
||||
if (null !== $entity->getUser()) {
|
||||
$user = $entity->getUser()->getDisplayName();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $user);
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['customer']) && !isset($columns['customer']['render'])) {
|
||||
$columns['customer']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$customer = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$customer = $entity->getProject()->getCustomer()->getName();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $customer);
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['project']) && !isset($columns['project']['render'])) {
|
||||
$columns['project']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$project = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$project = $entity->getProject()->getName();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $project);
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['activity']) && !isset($columns['activity']['render'])) {
|
||||
$columns['activity']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$activity = '';
|
||||
if (null !== $entity->getActivity()) {
|
||||
$activity = $entity->getActivity()->getName();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $activity);
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['description']) && !isset($columns['description']['render'])) {
|
||||
$columns['description']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $entity->getDescription());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['exported']) && !isset($columns['exported']['render'])) {
|
||||
$columns['exported']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$exported = $entity->isExported() ? 'entryState.exported' : 'entryState.not_exported';
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans($exported));
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['tags']) && !isset($columns['tags']['render'])) {
|
||||
$columns['tags']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, implode(',', $entity->getTagsAsArray()));
|
||||
};
|
||||
}
|
||||
|
||||
if ($showRates && isset($columns['hourlyRate']) && !isset($columns['hourlyRate']['render'])) {
|
||||
$columns['hourlyRate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$currency = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$currency = $entity->getProject()->getCustomer()->getCurrency();
|
||||
}
|
||||
$this->setRate($sheet, $column, $row, $entity->getHourlyRate(), $currency);
|
||||
};
|
||||
}
|
||||
|
||||
if ($showRates && isset($columns['fixedRate']) && !isset($columns['fixedRate']['render'])) {
|
||||
$columns['fixedRate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$currency = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$currency = $entity->getProject()->getCustomer()->getCurrency();
|
||||
}
|
||||
$this->setRate($sheet, $column, $row, $entity->getFixedRate(), $currency);
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['timesheet-meta'])) {
|
||||
$timesheetMetaFields = $this->findMetaColumns(new TimesheetMetaDisplayEvent($query, TimesheetMetaDisplayEvent::EXPORT));
|
||||
|
||||
$columns['timesheet-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($timesheetMetaFields) {
|
||||
foreach ($timesheetMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return count($timesheetMetaFields);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($timesheetMetaFields) {
|
||||
foreach ($timesheetMetaFields as $metaField) {
|
||||
$metaFieldValue = '';
|
||||
$metaField = $entity->getMetaField($metaField->getName());
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $metaFieldValue);
|
||||
}
|
||||
|
||||
return count($timesheetMetaFields);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($columns['customer-meta'])) {
|
||||
/** @var CustomerQuery $customerQuery */
|
||||
$customerQuery = $query->copyTo(new CustomerQuery());
|
||||
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($customerQuery, CustomerMetaDisplayEvent::EXPORT));
|
||||
|
||||
$columns['customer-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($customerMetaFields) {
|
||||
foreach ($customerMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return count($customerMetaFields);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($customerMetaFields) {
|
||||
foreach ($customerMetaFields as $metaField) {
|
||||
$metaFieldValue = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$metaField = $entity->getProject()->getCustomer()->getMetaField($metaField->getName());
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $metaFieldValue);
|
||||
}
|
||||
|
||||
return count($customerMetaFields);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($columns['project-meta'])) {
|
||||
$projectMetaFields = $this->findMetaColumns(new ProjectMetaDisplayEvent($query, ProjectMetaDisplayEvent::EXPORT));
|
||||
$columns['project-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($projectMetaFields) {
|
||||
foreach ($projectMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return count($projectMetaFields);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($projectMetaFields) {
|
||||
foreach ($projectMetaFields as $metaField) {
|
||||
$metaFieldValue = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$metaField = $entity->getProject()->getMetaField($metaField->getName());
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $metaFieldValue);
|
||||
}
|
||||
|
||||
return count($projectMetaFields);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($columns['activity-meta'])) {
|
||||
$activityMetaFields = $this->findMetaColumns(new ActivityMetaDisplayEvent($query, ActivityMetaDisplayEvent::EXPORT));
|
||||
$columns['activity-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($activityMetaFields) {
|
||||
foreach ($activityMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return count($activityMetaFields);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($activityMetaFields) {
|
||||
foreach ($activityMetaFields as $metaField) {
|
||||
$metaFieldValue = '';
|
||||
if (null !== $entity->getActivity()) {
|
||||
$metaField = $entity->getActivity()->getMetaField($metaField->getName());
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $metaFieldValue);
|
||||
}
|
||||
|
||||
return count($activityMetaFields);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($columns['user-meta'])) {
|
||||
$event = new UserPreferenceDisplayEvent(UserPreferenceDisplayEvent::EXPORT);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$userPreferences = $event->getPreferences();
|
||||
$columns['user-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($userPreferences) {
|
||||
foreach ($userPreferences as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return count($userPreferences);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($userPreferences) {
|
||||
foreach ($userPreferences as $preference) {
|
||||
$metaFieldValue = '';
|
||||
if (null !== $entity->getUser()) {
|
||||
$metaField = $entity->getUser()->getPreference($preference->getName());
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
}
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $metaFieldValue);
|
||||
}
|
||||
|
||||
return count($userPreferences);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (!$showRates) {
|
||||
$removes = ['rate', 'fixedRate', 'hourlyRate'];
|
||||
foreach ($removes as $removeMe) {
|
||||
if (array_key_exists($removeMe, $columns)) {
|
||||
unset($columns[$removeMe]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @return Spreadsheet
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||
*/
|
||||
protected function fromArrayToSpreadsheet(array $timesheets, TimesheetQuery $query): Spreadsheet
|
||||
protected function fromArrayToSpreadsheet(array $exportItems, TimesheetQuery $query): Spreadsheet
|
||||
{
|
||||
$customerQuery = $query->copyTo(new CustomerQuery());
|
||||
|
||||
$timesheetMetaFields = $this->findMetaColumns(new TimesheetMetaDisplayEvent($query, TimesheetMetaDisplayEvent::EXPORT));
|
||||
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($customerQuery, 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();
|
||||
|
||||
$showRates = $this->isRenderRate($query);
|
||||
$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'));
|
||||
if ($showRates) {
|
||||
$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'));
|
||||
if ($showRates) {
|
||||
$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()));
|
||||
$columns = $this->getColumns($exportItems, $query, $this->columns);
|
||||
|
||||
foreach ($columns as $label => $settings) {
|
||||
if (isset($settings['header'])) {
|
||||
$amount = $settings['header']($sheet, $recordsHeaderRow, $recordsHeaderColumn);
|
||||
$recordsHeaderColumn += $amount;
|
||||
} else {
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.' . $label));
|
||||
}
|
||||
}
|
||||
|
||||
$entryHeaderRow = $recordsHeaderRow + 1;
|
||||
@@ -228,111 +483,22 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$durationColumn = null;
|
||||
$rateColumn = null;
|
||||
|
||||
foreach ($timesheets as $timesheet) {
|
||||
foreach ($exportItems as $exportItem) {
|
||||
$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++;
|
||||
|
||||
if ($showRates) {
|
||||
$this->setRate($sheet, $entryHeaderColumn, $entryHeaderRow, $timesheet->getRate(), $customerCurrency);
|
||||
$rateColumn = $entryHeaderColumn;
|
||||
$entryHeaderColumn++;
|
||||
}
|
||||
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn, $entryHeaderRow, $timesheet->getUser()->getDisplayName());
|
||||
$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++;
|
||||
|
||||
if ($showRates) {
|
||||
$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();
|
||||
foreach ($columns as $label => $settings) {
|
||||
if ($label === 'duration') {
|
||||
$durationColumn = $entryHeaderColumn;
|
||||
} elseif ($label === 'rate') {
|
||||
$rateColumn = $entryHeaderColumn;
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($entryHeaderColumn++, $entryHeaderRow, $metaFieldValue);
|
||||
}
|
||||
foreach ($customerMetaFields as $metaField) {
|
||||
$metaField = $timesheet->getProject()->getCustomer()->getMetaField($metaField->getName());
|
||||
$metaFieldValue = '';
|
||||
if (null !== $metaField) {
|
||||
$metaFieldValue = $metaField->getValue();
|
||||
|
||||
if (!array_key_exists('render', $settings) || !is_callable($settings['render'])) {
|
||||
throw new \RuntimeException(sprintf('Missing renderer for export column %s', $label));
|
||||
}
|
||||
$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);
|
||||
|
||||
$amount = $settings['render']($sheet, $entryHeaderRow, $entryHeaderColumn, $exportItem);
|
||||
$entryHeaderColumn += (null === $amount) ? 1 : $amount;
|
||||
}
|
||||
|
||||
$entryHeaderRow++;
|
||||
@@ -360,15 +526,15 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
|
||||
*/
|
||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||
public function render(array $exportItems, TimesheetQuery $query): Response
|
||||
{
|
||||
$spreadsheet = $this->fromArrayToSpreadsheet($timesheets, $query);
|
||||
$spreadsheet = $this->fromArrayToSpreadsheet($exportItems, $query);
|
||||
$filename = $this->saveSpreadsheet($spreadsheet);
|
||||
|
||||
return $this->getFileResponse($filename, 'kimai-export' . $this->getFileExtension());
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
namespace App\Export\Base;
|
||||
|
||||
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\ExportItemInterface;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
@@ -54,7 +54,7 @@ class HtmlRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param ExportItemInterface[] $timesheets
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \Twig\Error\LoaderError
|
||||
@@ -63,6 +63,7 @@ class HtmlRenderer
|
||||
*/
|
||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||
{
|
||||
/** @var CustomerQuery $customerQuery */
|
||||
$customerQuery = $query->copyTo(new CustomerQuery());
|
||||
|
||||
$timesheetMetaFields = $this->findMetaColumns(new TimesheetMetaDisplayEvent($query, TimesheetMetaDisplayEvent::EXPORT));
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace App\Export\Base;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Export\ExportItemInterface;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Timesheet\UserDateTimeFactory;
|
||||
use App\Utils\HtmlToPdfConverter;
|
||||
@@ -47,7 +47,7 @@ class PDFRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param ExportItemInterface[] $timesheets
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \Twig\Error\LoaderError
|
||||
|
||||
@@ -9,28 +9,48 @@
|
||||
|
||||
namespace App\Export\Base;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Export\ExportItemInterface;
|
||||
|
||||
trait RendererTrait
|
||||
{
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @return array
|
||||
*/
|
||||
protected function calculateSummary(array $timesheets)
|
||||
protected function calculateSummary(array $exportItems)
|
||||
{
|
||||
$summary = [];
|
||||
|
||||
foreach ($timesheets as $timesheet) {
|
||||
$id = $timesheet->getProject()->getCustomer()->getId() . '_' . $timesheet->getProject()->getId();
|
||||
$activityId = $timesheet->getActivity()->getId();
|
||||
foreach ($exportItems as $exportItem) {
|
||||
$customerId = 'none';
|
||||
$customerName = '';
|
||||
$currency = null;
|
||||
$projectId = 'none';
|
||||
$projectName = '';
|
||||
$activityId = 'none';
|
||||
$activityName = '';
|
||||
|
||||
if (null !== $exportItem->getProject()) {
|
||||
$customerId = $exportItem->getProject()->getCustomer()->getId();
|
||||
$customerName = $exportItem->getProject()->getCustomer()->getName();
|
||||
$projectId = $exportItem->getProject()->getId();
|
||||
$projectName = $exportItem->getProject()->getName();
|
||||
$currency = $exportItem->getProject()->getCustomer()->getCurrency();
|
||||
}
|
||||
|
||||
if (null !== $exportItem->getActivity()) {
|
||||
$activityId = $exportItem->getActivity()->getId();
|
||||
$activityName = $exportItem->getActivity()->getName();
|
||||
}
|
||||
|
||||
$id = $customerId . '_' . $projectId;
|
||||
|
||||
if (!isset($summary[$id])) {
|
||||
$summary[$id] = [
|
||||
'customer' => $timesheet->getProject()->getCustomer()->getName(),
|
||||
'project' => $timesheet->getProject()->getName(),
|
||||
'customer' => $customerName,
|
||||
'project' => $projectName,
|
||||
'activities' => [],
|
||||
'currency' => $timesheet->getProject()->getCustomer()->getCurrency(),
|
||||
'currency' => $currency,
|
||||
'rate' => 0,
|
||||
'duration' => 0,
|
||||
];
|
||||
@@ -38,21 +58,21 @@ trait RendererTrait
|
||||
|
||||
if (!isset($summary[$id]['activities'][$activityId])) {
|
||||
$summary[$id]['activities'][$activityId] = [
|
||||
'activity' => $timesheet->getActivity()->getName(),
|
||||
'currency' => $timesheet->getProject()->getCustomer()->getCurrency(),
|
||||
'activity' => $activityName,
|
||||
'currency' => $currency,
|
||||
'rate' => 0,
|
||||
'duration' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$duration = $timesheet->getDuration();
|
||||
$duration = $exportItem->getDuration();
|
||||
if (null === $duration) {
|
||||
$duration = 0;
|
||||
}
|
||||
|
||||
$summary[$id]['rate'] += $timesheet->getRate();
|
||||
$summary[$id]['rate'] += $exportItem->getRate();
|
||||
$summary[$id]['duration'] += $duration;
|
||||
$summary[$id]['activities'][$activityId]['rate'] += $timesheet->getRate();
|
||||
$summary[$id]['activities'][$activityId]['rate'] += $exportItem->getRate();
|
||||
$summary[$id]['activities'][$activityId]['duration'] += $duration;
|
||||
}
|
||||
|
||||
|
||||
52
src/Export/ExportItemInterface.php
Normal file
52
src/Export/ExportItemInterface.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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\MetaTableTypeInterface;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
interface ExportItemInterface extends InvoiceItemInterface
|
||||
{
|
||||
/**
|
||||
* A name representation for this type of export.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string;
|
||||
|
||||
/**
|
||||
* A name representation for the category of this type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCategory(): string;
|
||||
|
||||
/**
|
||||
* Whether this item was already exported.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExported(): bool;
|
||||
|
||||
/**
|
||||
* Returns the named meta field or null.
|
||||
*
|
||||
* @param string $name
|
||||
* @return MetaTableTypeInterface|null
|
||||
*/
|
||||
public function getMetaField(string $name): ?MetaTableTypeInterface;
|
||||
|
||||
/**
|
||||
* Returns all assigned tag names.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTagsAsArray(): array;
|
||||
}
|
||||
@@ -9,18 +9,17 @@
|
||||
|
||||
namespace App\Export;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
interface RendererInterface
|
||||
{
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
*/
|
||||
public function render(array $timesheets, TimesheetQuery $query): Response;
|
||||
public function render(array $exportItems, TimesheetQuery $query): Response;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
|
||||
@@ -11,16 +11,7 @@ namespace App\Export\Timesheet;
|
||||
|
||||
use App\Export\Base\CsvRenderer as BaseCsvRenderer;
|
||||
use App\Export\TimesheetExportInterface;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
|
||||
final class CsvRenderer extends BaseCsvRenderer implements TimesheetExportInterface
|
||||
{
|
||||
protected function isRenderRate(TimesheetQuery $query): bool
|
||||
{
|
||||
if (null !== $query->getUser()) {
|
||||
return $this->voter->isGranted('view_rate_own_timesheet');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,7 @@ namespace App\Export\Timesheet;
|
||||
|
||||
use App\Export\Base\XlsxRenderer as BaseXlsxRenderer;
|
||||
use App\Export\TimesheetExportInterface;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
|
||||
final class XlsxRenderer extends BaseXlsxRenderer implements TimesheetExportInterface
|
||||
{
|
||||
protected function isRenderRate(TimesheetQuery $query): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
namespace App\Invoice\Calculator;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Export\ExportItemInterface;
|
||||
use App\Invoice\InvoiceItem;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
use App\Invoice\InvoiceItemWithAmountInterface;
|
||||
use App\Invoice\InvoiceItemWithTypeInterface;
|
||||
|
||||
abstract class AbstractMergedCalculator extends AbstractCalculator
|
||||
{
|
||||
public const TYPE_MIXED = 'mixed';
|
||||
public const CATEGORY_MIXED = 'mixed';
|
||||
|
||||
/**
|
||||
* @deprecated since 1.3 - will be removed with 2.0
|
||||
*/
|
||||
@@ -39,19 +42,19 @@ abstract class AbstractMergedCalculator extends AbstractCalculator
|
||||
$amount = $entry->getAmount();
|
||||
}
|
||||
|
||||
if ($entry instanceof InvoiceItemWithTypeInterface) {
|
||||
$type = $entry->getInvoiceType();
|
||||
$category = $entry->getInvoiceCategory();
|
||||
} else {
|
||||
$type = InvoiceItem::TYPE_TIMESHEET;
|
||||
$category = InvoiceItem::CATEGORY_TIMESHEET_WORK;
|
||||
$type = Timesheet::TYPE_TIMESHEET;
|
||||
$category = Timesheet::CATEGORY_WORK;
|
||||
|
||||
if ($entry instanceof ExportItemInterface) {
|
||||
$type = $entry->getType();
|
||||
$category = $entry->getCategory();
|
||||
}
|
||||
|
||||
if (null !== $invoiceItem->getType() && $type !== $invoiceItem->getType()) {
|
||||
$type = InvoiceItem::TYPE_MIXED;
|
||||
$type = self::TYPE_MIXED;
|
||||
}
|
||||
if (null !== $invoiceItem->getCategory() && $category !== $invoiceItem->getCategory()) {
|
||||
$category = InvoiceItem::CATEGORY_MIXED;
|
||||
$category = self::CATEGORY_MIXED;
|
||||
}
|
||||
|
||||
$invoiceItem->setType($type);
|
||||
|
||||
@@ -18,11 +18,6 @@ use App\Entity\User;
|
||||
*/
|
||||
final class InvoiceItem
|
||||
{
|
||||
public const TYPE_TIMESHEET = 'timesheet';
|
||||
public const CATEGORY_TIMESHEET_WORK = 'work';
|
||||
public const TYPE_MIXED = 'mixed';
|
||||
public const CATEGORY_MIXED = 'mixed';
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,9 @@ use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
|
||||
/**
|
||||
* @deprecated will be removed with 2.0 - use ExportItemInterface instead
|
||||
*/
|
||||
interface InvoiceItemInterface
|
||||
{
|
||||
public function getActivity(): ?Activity;
|
||||
|
||||
@@ -1,17 +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\Invoice;
|
||||
|
||||
interface InvoiceItemWithTypeInterface
|
||||
{
|
||||
public function getInvoiceType(): string;
|
||||
|
||||
public function getInvoiceCategory(): string;
|
||||
}
|
||||
@@ -91,6 +91,8 @@ class TimesheetQuery extends ActivityQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the data exclusively to the user (eg. users own timesheets).
|
||||
*
|
||||
* @return User|null
|
||||
*/
|
||||
public function getUser()
|
||||
@@ -99,6 +101,8 @@ class TimesheetQuery extends ActivityQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the data exclusively to the user (eg. users own timesheets).
|
||||
*
|
||||
* @param User|int|null $user
|
||||
* @return TimesheetQuery
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user