Reports:
Non-aggregated columns in Grid report export.
Normalized table mode for 2-dimensional Grid reports.
Ability to create internal reports via the UI.
Ability to show/hide and resize columns in the list report result view.
1559 lines
51 KiB
PHP
1559 lines
51 KiB
PHP
<?php
|
|
/***********************************************************************************
|
|
* The contents of this file are subject to the Extension License Agreement
|
|
* ("Agreement") which can be viewed at
|
|
* https://www.espocrm.com/extension-license-agreement/.
|
|
* By copying, installing downloading, or using this file, You have unconditionally
|
|
* agreed to the terms and conditions of the Agreement, and You may not use this
|
|
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
|
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
|
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
|
* usage to the software or any modified version or derivative work of the software
|
|
* created by or for you.
|
|
*
|
|
* Copyright (C) 2015-2026 EspoCRM, Inc.
|
|
*
|
|
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
|
************************************************************************************/
|
|
|
|
namespace Espo\Modules\Advanced\Business\Report;
|
|
|
|
use Espo\Core\Exceptions\Error;
|
|
use Espo\Core\Htmlizer\TemplateRendererFactory;
|
|
use Espo\Core\InjectableFactory;
|
|
use Espo\Core\Mail\EmailSender;
|
|
use Espo\Core\ORM\Type\FieldType;
|
|
use Espo\Core\Utils\Log;
|
|
use Espo\Entities\Email;
|
|
use Espo\Entities\User;
|
|
use Espo\Modules\Advanced\Entities\Report;
|
|
use Espo\Modules\Advanced\Tools\Report\Export\GridExportService;
|
|
use Espo\Modules\Advanced\Tools\Report\GridType\Data;
|
|
use Espo\Modules\Advanced\Tools\Report\GridType\Helper;
|
|
use Espo\Modules\Advanced\Tools\Report\GridType\Result as GridResult;
|
|
use Espo\ORM\EntityManager;
|
|
use Espo\Entities\Attachment;
|
|
use Espo\Entities\Preferences;
|
|
use Espo\Core\Utils\DateTime;
|
|
use Espo\Core\FileStorage\Manager as FileStorageManager;
|
|
use Espo\Core\Utils\Config;
|
|
use Espo\Core\Utils\Language;
|
|
use Espo\Core\Utils\Metadata;
|
|
use Espo\Core\Utils\TemplateFileManager;
|
|
use Exception;
|
|
use RuntimeException;
|
|
use stdClass;
|
|
|
|
class EmailBuilder
|
|
{
|
|
private DateTime $dateTime;
|
|
private Preferences $preferences;
|
|
|
|
public function __construct(
|
|
private Metadata $metadata,
|
|
private EntityManager $entityManager,
|
|
private Config $config,
|
|
private Language $language,
|
|
private TemplateFileManager $templateFileManager,
|
|
private FileStorageManager $fileStorageManager,
|
|
private Helper $gridHelper,
|
|
private GridExportService $gridExportService,
|
|
private InjectableFactory $injectableFactory,
|
|
private EmailSender $emailSender,
|
|
private TemplateRendererFactory $templateRendererFactory,
|
|
private Log $log,
|
|
) {}
|
|
|
|
private function initForUserById(string $userId): void
|
|
{
|
|
$user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $userId);
|
|
|
|
if (!$user) {
|
|
throw new RuntimeException('Report Sending Builder: No User with id = ' . $userId);
|
|
}
|
|
|
|
/** @var ?Preferences $preferences */
|
|
$preferences = $this->entityManager->getEntityById(Preferences::ENTITY_TYPE, $userId);
|
|
|
|
$this->preferences = $preferences;
|
|
|
|
$this->language = $this->injectableFactory->createWith(Language::class, [
|
|
'language' => $this->getPreference('language'),
|
|
'useCache' => $this->config->get('useCache'),
|
|
]);
|
|
|
|
$this->dateTime = new DateTime(
|
|
$this->getPreference('dateFormat'),
|
|
$this->getPreference('timeFormat'),
|
|
$this->getPreference('timeZone')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
private function getPreference(string $attribute)
|
|
{
|
|
if (
|
|
$this->preferences->get($attribute) !== null &&
|
|
$this->preferences->get($attribute) !== ''
|
|
) {
|
|
$this->preferences->get($attribute);
|
|
}
|
|
|
|
return $this->config->get($attribute);
|
|
}
|
|
|
|
/**
|
|
* @param stdClass $data
|
|
* @param GridResult|array<int, mixed> $reportResult
|
|
* @param bool $isLocal Images will be included.
|
|
* @throws Error
|
|
*/
|
|
public function buildEmailData($data, $reportResult, Report $report, bool $isLocal = false): void
|
|
{
|
|
if (!is_object($data) || !isset($data->userId)) {
|
|
throw new RuntimeException('Report Sending Builder: Not enough data for sending email.');
|
|
}
|
|
|
|
$this->initForUserById($data->userId);
|
|
|
|
$type = $report->getType();
|
|
|
|
switch ($type) {
|
|
case Report::TYPE_GRID:
|
|
case Report::TYPE_JOINT_GRID:
|
|
assert($reportResult instanceof GridResult);
|
|
|
|
$this->buildEmailGridData($data, $reportResult, $report);
|
|
|
|
break;
|
|
|
|
case Report::TYPE_LIST:
|
|
assert(is_array($reportResult));
|
|
|
|
$this->buildEmailListData($data, $reportResult, $report, $isLocal);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param stdClass $data
|
|
* @throws Error
|
|
*/
|
|
private function buildEmailGridData($data, GridResult $reportResult, Report $report): void
|
|
{
|
|
$depth = count($reportResult->getGroupByList());
|
|
|
|
if ($depth === 2) {
|
|
if ($reportResult->getTableMode() === Data::TABLE_MODE_NORMALIZED) {
|
|
$this->buildEmailGrid2NormalizedData($data, $reportResult, $report);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->buildEmailGrid2Data($data, $reportResult, $report);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->buildEmailGrid1Data($data, $reportResult, $report);
|
|
}
|
|
|
|
/**
|
|
* @param stdClass $data
|
|
* @param array<int, mixed> $reportResult
|
|
* @throws Error
|
|
*/
|
|
private function buildEmailListData($data, array $reportResult, Report $report, bool $isLocal): void
|
|
{
|
|
$entityType = $report->getTargetEntityType();
|
|
$columns = $report->getColumns();
|
|
$columnsData = get_object_vars($report->getColumnsData());
|
|
|
|
$fields = $this->metadata->get(['entityDefs', $entityType, 'fields']);
|
|
|
|
$columnAttributes = [];
|
|
|
|
foreach ($columns as $column) {
|
|
$columnData = $columnsData[$column] ?? null;
|
|
$attrs = [];
|
|
|
|
if (is_object($columnData)) {
|
|
if (isset($columnData->width)) {
|
|
$attrs['width'] = $columnData->width . '%';
|
|
}
|
|
|
|
if (isset($columnData->align)) {
|
|
$attrs['align'] = $columnData->align;
|
|
}
|
|
}
|
|
|
|
$columnAttributes[$column] = $attrs;
|
|
}
|
|
|
|
$columnTitles = [];
|
|
|
|
foreach ($columns as $column) {
|
|
$field = $column;
|
|
$scope = $entityType;
|
|
$isForeign = false;
|
|
|
|
if (str_contains($column, '.')) {
|
|
$isForeign = true;
|
|
|
|
[$link, $field] = explode('.', $column);
|
|
|
|
$scope = $this->metadata->get(['entityDefs', $entityType, 'links', $link, 'entity']);
|
|
$fields[$column] = $this->metadata->get(['entityDefs', $scope, 'fields', $field]);
|
|
$fields[$column]['scope'] = $scope;
|
|
$fields[$column]['isForeign'] = true;
|
|
}
|
|
|
|
$label = $this->language->translateLabel($field, 'fields', $scope);
|
|
|
|
if ($isForeign) {
|
|
$label = $this->language->translateLabel($link ?? '', 'links', $entityType) . ' . ' . $label;
|
|
}
|
|
|
|
$columnTitles[] = [
|
|
'label' => $label,
|
|
'attrs' => $columnAttributes[$column],
|
|
'value' => $label,
|
|
'isBold' => true,
|
|
];
|
|
}
|
|
|
|
$rows = [];
|
|
|
|
foreach ($reportResult as $recordKey => $record) {
|
|
foreach ($columns as $columnKey => $column) {
|
|
$type = (isset($fields[$column])) ? $fields[$column]['type'] : '';
|
|
$columnInRecord = str_replace('.', '_', $column);
|
|
|
|
$value = $record[$columnInRecord] ?? null;
|
|
$value = is_scalar($value) ? (string) $record[$columnInRecord] : '';
|
|
|
|
switch ($type) {
|
|
case FieldType::DATE:
|
|
if (!empty($value)) {
|
|
$value = $this->dateTime->convertSystemDate($value);
|
|
}
|
|
|
|
break;
|
|
|
|
case FieldType::DATETIME:
|
|
case FieldType::DATETIME_OPTIONAL:
|
|
if (!empty($value)) {
|
|
$value = $this->dateTime->convertSystemDateTime($value);
|
|
}
|
|
|
|
break;
|
|
|
|
case FieldType::LINK:
|
|
case FieldType::LINK_PARENT:
|
|
case FieldType::LINK_ONE:
|
|
if (!empty($record[$columnInRecord . 'Name'])) {
|
|
$value = $record[$columnInRecord . 'Name'];
|
|
}
|
|
|
|
break;
|
|
|
|
case FieldType::LINK_MULTIPLE:
|
|
case FieldType::ATTACHMENT_MULTIPLE:
|
|
if (!empty($record[$columnInRecord . 'Names'])) {
|
|
$value = implode(', ', array_values( (array) $record[$columnInRecord . 'Names']));
|
|
}
|
|
|
|
break;
|
|
|
|
case 'jsonArray':
|
|
break;
|
|
|
|
case FieldType::BOOL:
|
|
$value = ($value) ? '1' : '0';
|
|
|
|
break;
|
|
|
|
case FieldType::ENUM:
|
|
if (isset($fields[$column]['isForeign']) && $fields[$column]['isForeign']) {
|
|
[, $field] = explode('.', $column);
|
|
|
|
$value = $this->language->translateOption($value, $field, $fields[$column]['scope']);
|
|
}
|
|
else {
|
|
$value = $this->language->translateOption($value, $column, $entityType);
|
|
}
|
|
|
|
break;
|
|
|
|
case FieldType::INT:
|
|
$value = $this->formatInt($value);
|
|
|
|
break;
|
|
|
|
case FieldType::FLOAT:
|
|
case 'decimal':
|
|
$isCurrency = isset($fields[$column]['view']) &&
|
|
// @todo Refactor.
|
|
strpos($fields[$column]['view'], 'currency-converted');
|
|
|
|
$decimalPlaces = $fields[$column]['decimalPlaces'] ?? null;
|
|
|
|
$value = ($isCurrency) ?
|
|
$this->formatCurrency($value, null, $decimalPlaces) :
|
|
$this->formatFloat($value, $decimalPlaces);
|
|
|
|
break;
|
|
|
|
case FieldType::CURRENCY:
|
|
$decimalPlaces = $fields[$column]['decimalPlaces'] ?? null;
|
|
|
|
$value = $this->formatCurrency($value, $record[$columnInRecord . 'Currency'], $decimalPlaces);
|
|
|
|
break;
|
|
|
|
case FieldType::CURRENCY_CONVERTED:
|
|
$decimalPlaces = $fields[$column]['decimalPlaces'] ?? null;
|
|
|
|
$value = $this->formatCurrency($value, null, $decimalPlaces);
|
|
|
|
break;
|
|
|
|
case FieldType::ADDRESS:
|
|
$value = '';
|
|
|
|
if (!empty($record[$columnInRecord . 'Street'])) {
|
|
$value = $record[$columnInRecord.'Street'];
|
|
}
|
|
|
|
if (
|
|
!empty($record[$columnInRecord.'City']) ||
|
|
!empty($record[$columnInRecord.'State']) ||
|
|
!empty($record[$columnInRecord.'PostalCode'])
|
|
) {
|
|
if ($value) {
|
|
$value .= " ";
|
|
}
|
|
|
|
if (!empty($record[$columnInRecord.'City'])) {
|
|
$value .= $record[$columnInRecord.'City'];
|
|
|
|
if (
|
|
!empty($record[$columnInRecord.'State']) ||
|
|
!empty($record[$columnInRecord.'PostalCode'])
|
|
) {
|
|
$value .= ', ';
|
|
}
|
|
}
|
|
|
|
if (!empty($record[$columnInRecord.'State'])) {
|
|
$value .= $record[$columnInRecord.'State'];
|
|
|
|
if (!empty($record[$columnInRecord.'PostalCode'])) {
|
|
$value .= ' ';
|
|
}
|
|
}
|
|
|
|
if (!empty($record[$columnInRecord.'PostalCode'])) {
|
|
$value .= $record[$columnInRecord.'PostalCode'];
|
|
}
|
|
}
|
|
|
|
if (!empty($record[$columnInRecord.'Country'])) {
|
|
if ($value) {
|
|
$value .= " ";
|
|
}
|
|
|
|
$value .= $record[$columnInRecord.'Country'];
|
|
}
|
|
break;
|
|
|
|
case FieldType::ARRAY:
|
|
case FieldType::MULTI_ENUM:
|
|
case FieldType::CHECKLIST:
|
|
|
|
$value = $record[$columnInRecord] ?? [];
|
|
|
|
if (is_array($value)) {
|
|
$value = implode(", ", $value);
|
|
} else {
|
|
$value = '';
|
|
}
|
|
|
|
break;
|
|
|
|
case FieldType::IMAGE:
|
|
if (!$isLocal) {
|
|
break;
|
|
}
|
|
|
|
$attachmentId = $record[$columnInRecord . 'Id'] ?? null;
|
|
|
|
if ($attachmentId) {
|
|
/** @var ?Attachment $attachment */
|
|
$attachment = $this->entityManager
|
|
->getEntityById(Attachment::ENTITY_TYPE, $attachmentId);
|
|
|
|
if ($attachment) {
|
|
$filePath = $this->fileStorageManager->getLocalFilePath($attachment);
|
|
|
|
$value = "<img src=\"$filePath\" alt=\"image\">";
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
$rows[$recordKey][$columnKey] = [
|
|
'value' => $value,
|
|
'attrs' => $columnAttributes[$column],
|
|
];
|
|
}
|
|
}
|
|
$bodyData = [
|
|
'columnList' => $columnTitles,
|
|
'rowList' => $rows,
|
|
'noDataLabel' => $this->language->translateLabel('No Data'),
|
|
];
|
|
|
|
try {
|
|
$subject = $this->renderReport($report, 'reportSendingList', 'subject');
|
|
$body = $this->renderReport($report, 'reportSendingList', 'body', $bodyData);
|
|
}
|
|
catch (Exception $e) {
|
|
$this->log->error($e->getMessage());
|
|
|
|
throw Error::createWithBody(
|
|
'emailTemplateParsingError',
|
|
Error\Body::create()
|
|
->withMessageTranslation('emailTemplateParsingError', 'Report',
|
|
['template' => 'reportSendingList'])
|
|
->encode()
|
|
);
|
|
}
|
|
|
|
$data->emailSubject = $subject;
|
|
$data->emailBody = $body;
|
|
$data->tableData = array_merge([$columnTitles], $rows);
|
|
}
|
|
|
|
/**
|
|
* @param stdClass $data
|
|
* @throws Error
|
|
*/
|
|
private function buildEmailGrid1Data($data, GridResult $reportResult, Report $report): void
|
|
{
|
|
$reportData = $reportResult->getReportData();
|
|
|
|
/** @var array<string, int> $columnDecimalPlacesMap */
|
|
$columnDecimalPlacesMap = get_object_vars($reportResult->getColumnDecimalPlacesMap());
|
|
|
|
$groupCount = count($reportResult->getGroupByList());
|
|
|
|
if ($groupCount) {
|
|
$groupName = $reportResult->getGroupByList()[0];
|
|
} else {
|
|
$groupName = '__STUB__';
|
|
}
|
|
|
|
$rows = [];
|
|
|
|
$row = [];
|
|
|
|
$row[] = ['value' => ''];
|
|
|
|
$columnTypes = [];
|
|
|
|
$hasSubListColumns = count($reportResult->getSubListColumnList()) > 0;
|
|
|
|
foreach ($reportResult->getColumnList() as $column) {
|
|
$allowedTypeList = [
|
|
'int',
|
|
'float',
|
|
'decimal',
|
|
'currency',
|
|
'currencyConverted',
|
|
];
|
|
|
|
$columnType = null;
|
|
$function = null;
|
|
|
|
if (strpos($column, ':')) {
|
|
[$function, $field] = explode(':', $column);
|
|
} else {
|
|
$field = $column;
|
|
}
|
|
|
|
$fieldEntityType = $report->getTargetEntityType();
|
|
|
|
$columnType = $reportResult->getColumnTypeMap()[$column] ?? null;
|
|
|
|
if (strpos($column, '.')) {
|
|
[$link, $field] = explode('.', $column);
|
|
|
|
$fieldEntityType = $this->metadata->get(['entityDefs', $fieldEntityType, 'links', $link, 'entity']);
|
|
}
|
|
|
|
if (!$columnType) {
|
|
if ($function === 'COUNT') {
|
|
$columnType = 'int';
|
|
} else {
|
|
$columnType = $this->metadata->get(['entityDefs', $fieldEntityType, 'fields', $field, 'type']);
|
|
}
|
|
}
|
|
|
|
$columnTypes[$column] = (in_array($columnType, $allowedTypeList)) ? $columnType : 'float';
|
|
|
|
$label = $column;
|
|
|
|
if (isset($reportResult->getColumnNameMap()[$column])) {
|
|
$label = $reportResult->getColumnNameMap()[$column];
|
|
}
|
|
|
|
$row[] = [
|
|
'value' => $label,
|
|
'isBold' => true,
|
|
];
|
|
}
|
|
|
|
$rows[] = $row;
|
|
|
|
foreach ($reportResult->getGrouping()[0] ?? [] as $gr) {
|
|
$rows[] = $this->buildEmailGrid1GroupingRow(
|
|
gr: $gr,
|
|
groupName: $groupName,
|
|
reportData: $reportData,
|
|
reportResult: $reportResult,
|
|
columnTypes: $columnTypes,
|
|
columnDecimalPlacesMap: $columnDecimalPlacesMap,
|
|
);
|
|
|
|
if ($hasSubListColumns) {
|
|
$rows = array_merge(
|
|
$rows,
|
|
$this->buildEmailGrid1SubListRowList(
|
|
gr: $gr,
|
|
reportResult: $reportResult,
|
|
columnTypes: $columnTypes,
|
|
columnDecimalPlacesMap: $columnDecimalPlacesMap,
|
|
)
|
|
);
|
|
|
|
$rows[] = $this->buildEmailGrid1GroupingRow(
|
|
gr: $gr,
|
|
groupName: $groupName,
|
|
reportData: $reportData,
|
|
reportResult: $reportResult,
|
|
columnTypes: $columnTypes,
|
|
columnDecimalPlacesMap: $columnDecimalPlacesMap,
|
|
onlyNumeric: true,
|
|
);
|
|
}
|
|
}
|
|
|
|
if ($groupCount) {
|
|
$row = [];
|
|
|
|
$totalLabel = $this->language->translateLabel('Total', 'labels', 'Report');
|
|
|
|
$row[] = [
|
|
'value' => $totalLabel,
|
|
'isBold' => true,
|
|
];
|
|
|
|
foreach ($reportResult->getColumnList() as $column) {
|
|
if (
|
|
in_array($column, $reportResult->getNumericColumnList()) &&
|
|
in_array($column, $reportResult->getAggregatedColumnList())
|
|
) {
|
|
$sum = 0;
|
|
|
|
if (isset($reportResult->getSums()->$column)) {
|
|
$sum = $reportResult->getSums()->$column;
|
|
|
|
switch ($columnTypes[$column]) {
|
|
case 'int':
|
|
$sum = $this->formatInt($sum);
|
|
|
|
break;
|
|
|
|
case 'float':
|
|
case 'decimal':
|
|
$decimalPlaces = $columnDecimalPlacesMap[$column] ?? null;
|
|
|
|
$sum = $this->formatFloat($sum, $decimalPlaces);
|
|
|
|
break;
|
|
|
|
case 'currency':
|
|
case 'currencyConverted':
|
|
$decimalPlaces = $columnDecimalPlacesMap[$column] ?? null;
|
|
|
|
$sum = $this->formatCurrency($sum, $reportResult->getCurrency(), $decimalPlaces);
|
|
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
$sum = '';
|
|
}
|
|
|
|
$row[] = [
|
|
'value' => $sum,
|
|
'isBold' => true,
|
|
'attrs' => ['align' => 'right'],
|
|
];
|
|
}
|
|
|
|
$rows[] = $row;
|
|
} else {
|
|
foreach ($rows as &$row) {
|
|
unset($row[0]);
|
|
|
|
$row = array_values($row);
|
|
}
|
|
}
|
|
|
|
$this->prepareGrid1Result($rows, $report, $data);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string> $columnTypes
|
|
* @param array<string, int> $columnDecimalPlacesMap
|
|
* @return (array<string, mixed>)[]
|
|
*/
|
|
private function buildEmailGrid1GroupingRow(
|
|
string $gr,
|
|
string $groupName,
|
|
object $reportData,
|
|
GridResult $reportResult,
|
|
array $columnTypes,
|
|
array $columnDecimalPlacesMap,
|
|
bool $onlyNumeric = false,
|
|
): array {
|
|
|
|
$row = [];
|
|
|
|
$hasSubListColumns = count($reportResult->getSubListColumnList()) > 0;
|
|
|
|
$label = $gr;
|
|
|
|
if (empty($label)) {
|
|
$label = $this->language->translateLabel('-Empty-', 'labels', 'Report');
|
|
} else if (isset($reportResult->getGroupValueMap()[$groupName][$gr])) {
|
|
$label = $reportResult->getGroupValueMap()[$groupName][$gr];
|
|
}
|
|
|
|
if (strpos($groupName , ':')) {
|
|
[$function,] = explode(':', $groupName);
|
|
|
|
$label = $this->handleDateGroupValue($function, $label);
|
|
}
|
|
|
|
if ($hasSubListColumns && $onlyNumeric) {
|
|
$label = $this->language->translateLabel('Group Total', 'labels', 'Report');
|
|
}
|
|
|
|
$row[] = [
|
|
'value' => $label,
|
|
'isBold' => $hasSubListColumns && !$onlyNumeric,
|
|
];
|
|
|
|
foreach ($reportResult->getColumnList() as $column) {
|
|
$isNumericValue = in_array($column, $reportResult->getNumericColumnList());
|
|
|
|
if ($hasSubListColumns && !$onlyNumeric && $isNumericValue) {
|
|
$row[] = [];
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($hasSubListColumns && $onlyNumeric && !$isNumericValue) {
|
|
$row[] = [];
|
|
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
$isNumericValue &&
|
|
!in_array($column, $reportResult->getAggregatedColumnList())
|
|
) {
|
|
$row[] = [];
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($isNumericValue) {
|
|
$value = $this->formatColumnValue(
|
|
value: $reportData->$gr->$column ?? 0,
|
|
type: $columnTypes[$column],
|
|
decimalPlaces: $columnDecimalPlacesMap[$column] ?? null,
|
|
currency: $reportResult->getCurrency(),
|
|
);
|
|
|
|
$align = 'right';
|
|
} else {
|
|
$value = $reportData->$gr->$column ?? null;
|
|
|
|
if ($value !== null) {
|
|
$value = $reportResult->getCellValueMaps()->$column->$value ?? $value;
|
|
}
|
|
|
|
$align = 'left';
|
|
}
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => [
|
|
'align' => $align,
|
|
],
|
|
];
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
private function formatColumnValue(
|
|
mixed $value,
|
|
?string $type,
|
|
?int $decimalPlaces = null,
|
|
?string $currency = null,
|
|
): string {
|
|
|
|
if ((!$type || $type === 'int') && $decimalPlaces !== null) {
|
|
$type = 'float';
|
|
}
|
|
|
|
return match ($type) {
|
|
'int' => $this->formatInt($value),
|
|
'float' => $this->formatFloat($value, $decimalPlaces),
|
|
'currency', 'currencyConverted' => $this->formatCurrency($value, $currency, $decimalPlaces),
|
|
default => (string) $value,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string> $columnTypes
|
|
* @param array<string, int> $columnDecimalPlacesMap
|
|
* @return array{value: mixed, isBold?: bool, attrs?: array<string, mixed>}[][]
|
|
*/
|
|
private function buildEmailGrid1SubListRowList(
|
|
string $gr,
|
|
GridResult $reportResult,
|
|
array $columnTypes,
|
|
array $columnDecimalPlacesMap,
|
|
): array {
|
|
|
|
$itemList = $reportResult->getSubListData()->$gr;
|
|
|
|
$rowList = [];
|
|
|
|
foreach ($itemList as $item) {
|
|
$rowList[] = $this->buildEmailGrid1SubListRow(
|
|
item: $item,
|
|
reportResult: $reportResult,
|
|
columnTypes: $columnTypes,
|
|
columnDecimalPlacesMap: $columnDecimalPlacesMap,
|
|
);
|
|
}
|
|
|
|
return $rowList;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string> $columnTypes
|
|
* @param array<string, int> $columnDecimalPlacesMap
|
|
* @return array{value: mixed, isBold?: bool, attrs?: array<string, mixed>}[]
|
|
*/
|
|
private function buildEmailGrid1SubListRow(
|
|
object $item,
|
|
GridResult $reportResult,
|
|
array $columnTypes,
|
|
array $columnDecimalPlacesMap
|
|
): array {
|
|
|
|
$row = [];
|
|
|
|
$row[] = [
|
|
'value' => '',
|
|
];
|
|
|
|
foreach ($reportResult->getColumnList() as $column) {
|
|
if (!in_array($column, $reportResult->getSubListColumnList())) {
|
|
$row[] = [
|
|
'value' => '',
|
|
];
|
|
|
|
continue;
|
|
}
|
|
|
|
$isNumericValue = in_array($column, $reportResult->getNumericColumnList());
|
|
|
|
if ($isNumericValue) {
|
|
$value = $this->formatColumnValue(
|
|
value: $item->$column ?? 0,
|
|
type: $columnTypes[$column],
|
|
decimalPlaces: $columnDecimalPlacesMap[$column] ?? null
|
|
);
|
|
} else {
|
|
$value = $item->$column ?? '';
|
|
}
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => ['align' => $isNumericValue ? 'right' : 'left'],
|
|
];
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param stdClass $data
|
|
* @throws Error
|
|
*/
|
|
private function buildEmailGrid2NormalizedData($data, GridResult $reportResult, Report $report): void
|
|
{
|
|
$groupName1 = $reportResult->getGroupByList()[0];
|
|
$groupName2 = $reportResult->getGroupByList()[1];
|
|
|
|
/** @var array<string, int> $columnDecimalPlacesMap */
|
|
$columnDecimalPlacesMap = get_object_vars($reportResult->getColumnDecimalPlacesMap());
|
|
|
|
$rows = [];
|
|
|
|
$row = [
|
|
[
|
|
'value' => $reportResult->getGroupNameMap()[$groupName1] ?? null,
|
|
'isBold' => true,
|
|
],
|
|
[
|
|
'value' => $reportResult->getGroupNameMap()[$groupName2] ?? null,
|
|
'isBold' => true,
|
|
],
|
|
];
|
|
|
|
foreach ($reportResult->getNonSummaryColumnList() as $column) {
|
|
$row[] = [
|
|
'value' => $reportResult->getColumnNameMap()[$column] ?? null,
|
|
'isBold' => true,
|
|
];
|
|
}
|
|
|
|
foreach ($reportResult->getSummaryColumnList() as $column) {
|
|
$row[] = [
|
|
'value' => $reportResult->getColumnNameMap()[$column] ?? null,
|
|
'isBold' => true,
|
|
];
|
|
}
|
|
|
|
$rows[] = $row;
|
|
|
|
foreach ($reportResult->getGrouping()[0] ?? [] as $group) {
|
|
foreach ($reportResult->getGrouping()[1] ?? [] as $secondGroup) {
|
|
$row = [];
|
|
|
|
$row[] = [
|
|
'value' => $this->prepareGroupLabel($reportResult, $groupName1, $group),
|
|
];
|
|
|
|
$row[] = [
|
|
'value' => $this->prepareGroupLabel($reportResult, $groupName2, $secondGroup),
|
|
];
|
|
|
|
foreach ($reportResult->getNonSummaryColumnList() as $column) {
|
|
$columnGroup = $reportResult->getNonSummaryColumnGroupMap()->$column ?? null;
|
|
|
|
$columnGroupValue = $columnGroup === $reportResult->getGroupByList()[0] ?
|
|
$group : $secondGroup;
|
|
|
|
$value = $this->gridExportService->getCellDisplayValueFromResult(
|
|
groupIndex: $columnGroup === $reportResult->getGroupByList()[0] ? 0 : 1,
|
|
groupValue: $columnGroupValue,
|
|
column: $column,
|
|
reportResult: $reportResult,
|
|
);
|
|
|
|
[$value, $align] = $this->prepareColumnDisplayData(
|
|
reportResult: $reportResult,
|
|
column: $column,
|
|
value: $value,
|
|
);
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => ['align' => $align],
|
|
];
|
|
}
|
|
|
|
$hasNonEmpty = false;
|
|
|
|
foreach ($reportResult->getSummaryColumnList() as $column) {
|
|
$value = $reportResult->getReportData()->$group->$secondGroup->$column ?? null;
|
|
|
|
if ($value !== null) {
|
|
$hasNonEmpty = true;
|
|
}
|
|
|
|
$value = $this->formatColumnValue(
|
|
value: $value,
|
|
type: $this->getNumericColumnType($column, $report),
|
|
decimalPlaces: $columnDecimalPlacesMap[$column] ?? null,
|
|
currency: $reportResult->getCurrency(),
|
|
);
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => ['align' => 'right'],
|
|
];
|
|
}
|
|
|
|
if (!$hasNonEmpty) {
|
|
continue;
|
|
}
|
|
|
|
$rows[] = $row;
|
|
}
|
|
}
|
|
|
|
$row = [];
|
|
|
|
$row[] = [
|
|
'value' => $this->language->translateLabel('Total', 'labels', 'Report'),
|
|
'isBold' => true,
|
|
];
|
|
|
|
$row[] = [];
|
|
|
|
foreach ($reportResult->getNonSummaryColumnList() as $ignored) {
|
|
$row[] = [];
|
|
}
|
|
|
|
foreach ($reportResult->getSummaryColumnList() as $column) {
|
|
$sum = $reportResult->getSums()->$column ?? 0;
|
|
|
|
$value = $this->formatColumnValue(
|
|
value: $sum ,
|
|
type: $this->getNumericColumnType($column, $report),
|
|
decimalPlaces: $columnDecimalPlacesMap[$column] ?? null,
|
|
currency: $reportResult->getCurrency(),
|
|
);
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'isBold' => true,
|
|
'attrs' => ['align' => 'right'],
|
|
];
|
|
}
|
|
|
|
$rows[] = $row;
|
|
|
|
$this->prepareGrid1Result($rows, $report, $data);
|
|
}
|
|
|
|
/**
|
|
* @param stdClass $data
|
|
* @throws Error
|
|
*/
|
|
private function buildEmailGrid2Data($data, GridResult $reportResult, Report $report): void
|
|
{
|
|
$reportData = $reportResult->getReportData();
|
|
|
|
/** @var array<string, int> $columnDecimalPlacesMap */
|
|
$columnDecimalPlacesMap = get_object_vars($reportResult->getColumnDecimalPlacesMap());
|
|
|
|
$specificColumn = $data->specificColumn ?? null;
|
|
|
|
$grids = [];
|
|
|
|
foreach ($reportResult->getSummaryColumnList() as $column) {
|
|
$groupName1 = $reportResult->getGroupByList()[0];
|
|
$groupName2 = $reportResult->getGroupByList()[1];
|
|
|
|
if ($specificColumn && $specificColumn !== $column) {
|
|
continue;
|
|
}
|
|
|
|
$group1NonSummaryColumnList = $reportResult->getGroup1NonSummaryColumnList() ?? [];
|
|
$group2NonSummaryColumnList = $reportResult->getGroup2NonSummaryColumnList() ?? [];
|
|
|
|
$columnType = $this->getNumericColumnType($column, $report);
|
|
|
|
$columnTypes[$column] = $columnType;
|
|
|
|
$grid = [];
|
|
$row = [];
|
|
$row[] = [];
|
|
|
|
foreach ($group2NonSummaryColumnList as $c) {
|
|
$text = $reportResult->getColumnNameMap()[$c];
|
|
|
|
$row[] = ['value' => $text];
|
|
}
|
|
|
|
foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) {
|
|
$label = $this->prepareGroupLabel($reportResult, $groupName1, $gr1);
|
|
|
|
$row[] = ['value' => $label];
|
|
}
|
|
|
|
if ($reportResult->getGroup2Sums() !== null) {
|
|
$row[] = [
|
|
'value' => $this->language->translateLabel('Total', 'labels', 'Report'),
|
|
'isBold' => true,
|
|
];
|
|
}
|
|
|
|
$grid[] = $row;
|
|
|
|
foreach ($group1NonSummaryColumnList as $c) {
|
|
$row = [];
|
|
|
|
$text = $reportResult->getColumnNameMap()[$c];
|
|
$row[] = ['value' => $text];
|
|
|
|
foreach ($group2NonSummaryColumnList as $ignored3) {
|
|
$row[] = [];
|
|
}
|
|
|
|
foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) {
|
|
$value = $this->gridExportService->getCellDisplayValueFromResult(
|
|
groupIndex: 0,
|
|
groupValue: $gr1,
|
|
column: $c,
|
|
reportResult: $reportResult,
|
|
);
|
|
|
|
[$value, $align] = $this->prepareColumnDisplayData(
|
|
reportResult: $reportResult,
|
|
column: $c,
|
|
value: $value,
|
|
);
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => ['align' => $align],
|
|
];
|
|
}
|
|
|
|
$row[] = [];
|
|
|
|
$grid[] = $row;
|
|
}
|
|
|
|
foreach ($reportResult->getGrouping()[1] ?? [] as $gr2) {
|
|
$row = [];
|
|
$label = $gr2;
|
|
|
|
if (empty($label)) {
|
|
$label = $this->language->translateLabel('-Empty-', 'labels', 'Report');
|
|
} else if (isset($reportResult->getGroupValueMap()[$groupName2][$gr2])) {
|
|
$label = $reportResult->getGroupValueMap()[$groupName2][$gr2];
|
|
}
|
|
|
|
if (strpos($groupName2 , ':')) {
|
|
[$function, $field] = explode(':', $groupName2);
|
|
|
|
$label = $this->handleDateGroupValue($function, $label);
|
|
}
|
|
|
|
$row[] = [
|
|
'value' => $label,
|
|
'isBold' => true,
|
|
];
|
|
|
|
foreach ($group2NonSummaryColumnList as $c) {
|
|
$value = $this->gridExportService->getCellDisplayValueFromResult(
|
|
groupIndex: 1,
|
|
groupValue: $gr2,
|
|
column: $c,
|
|
reportResult: $reportResult,
|
|
);
|
|
|
|
[$value, $align] = $this->prepareColumnDisplayData(
|
|
reportResult: $reportResult,
|
|
column: $c,
|
|
value: $value,
|
|
);
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => ['align' => $align],
|
|
];
|
|
}
|
|
|
|
foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) {
|
|
$value = 0;
|
|
|
|
if (isset($reportData->$gr1->$gr2->$column)) {
|
|
$value = $reportData->$gr1->$gr2->$column;
|
|
|
|
$value = $this->formatColumnValue(
|
|
value: $value,
|
|
type: $columnTypes[$column],
|
|
decimalPlaces: $columnDecimalPlacesMap[$column] ?? null,
|
|
currency: $reportResult->getCurrency(),
|
|
);
|
|
}
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => ['align' => 'right'],
|
|
];
|
|
}
|
|
|
|
if ($reportResult->getGroup2Sums() !== null) {
|
|
$value = $reportResult->getGroup2Sums()->$gr2->$column;
|
|
|
|
$value = $this->formatColumnValue(
|
|
value: $value ?? 0,
|
|
type: $columnTypes[$column],
|
|
decimalPlaces: $columnDecimalPlacesMap[$column] ?? null,
|
|
currency: $reportResult->getCurrency(),
|
|
);
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => ['align' => 'right'],
|
|
'isBold' => true,
|
|
];
|
|
}
|
|
|
|
$grid[] = $row;
|
|
}
|
|
|
|
$row = [];
|
|
|
|
$row[] = [
|
|
'value' => $this->language->translateLabel('Total', 'labels', 'Report'),
|
|
'isBold' => true,
|
|
];
|
|
|
|
foreach ($group2NonSummaryColumnList as $ignored) {
|
|
$row[] = ['value' => ''];
|
|
}
|
|
|
|
foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) {
|
|
$sum = 0;
|
|
|
|
if (
|
|
!empty($reportResult->getGroup1Sums()->$gr1) &&
|
|
!empty($reportResult->getGroup1Sums()->$gr1->$column)
|
|
) {
|
|
$sum = $reportResult->getGroup1Sums()->$gr1->$column;
|
|
|
|
$sum = $this->formatColumnValue(
|
|
value: $sum ,
|
|
type: $columnTypes[$column],
|
|
decimalPlaces: $columnDecimalPlacesMap[$column] ?? null,
|
|
currency: $reportResult->getCurrency(),
|
|
);
|
|
}
|
|
|
|
$row[] = [
|
|
'value' => $sum,
|
|
'isBold' => true,
|
|
'attrs' => ['align' => 'right'],
|
|
];
|
|
}
|
|
|
|
if ($reportResult->getGroup2Sums() !== null) {
|
|
$value = $reportResult->getSums()->$column;
|
|
|
|
$value = $this->formatColumnValue(
|
|
value: $value ?? 0,
|
|
type: $columnTypes[$column],
|
|
decimalPlaces: $columnDecimalPlacesMap[$column] ?? null,
|
|
currency: $reportResult->getCurrency(),
|
|
);
|
|
|
|
$row[] = [
|
|
'value' => $value,
|
|
'attrs' => ['align' => 'right'],
|
|
'isBold' => true,
|
|
];
|
|
}
|
|
|
|
$grid[] = $row;
|
|
|
|
$rows = $grid;
|
|
|
|
$grids[] = [
|
|
'rowList' => $rows,
|
|
'header' => $reportResult->getColumnNameMap()[$column],
|
|
];
|
|
}
|
|
|
|
$bodyData = ['gridList' => $grids];
|
|
|
|
try {
|
|
$subject = $this->renderReport($report, 'reportSendingGrid2', 'subject');
|
|
$body = $this->renderReport($report, 'reportSendingGrid2', 'body', $bodyData);
|
|
} catch (Exception $e) {
|
|
$this->log->error($e->getMessage());
|
|
|
|
throw Error::createWithBody(
|
|
'emailTemplateParsingError',
|
|
Error\Body::create()
|
|
->withMessageTranslation('emailTemplateParsingError', 'Report',
|
|
['template' => 'reportSendingGrid2'])
|
|
->encode()
|
|
);
|
|
}
|
|
|
|
$data->emailSubject = $subject;
|
|
$data->emailBody = $body;
|
|
|
|
if (count($grids)) {
|
|
$data->tableData = $grids[0]['rowList'];
|
|
} else {
|
|
$data->tableData = [];
|
|
}
|
|
}
|
|
|
|
public function sendEmail(
|
|
string $userId,
|
|
string $emailSubject,
|
|
string $emailBody,
|
|
?string $attachmentId,
|
|
): void {
|
|
|
|
if (!$emailSubject || !$emailBody) {
|
|
throw new RuntimeException('Not enough data for sending report email.');
|
|
}
|
|
|
|
$user = $this->entityManager->getEntity('User', $userId);
|
|
|
|
if (!$user) {
|
|
throw new RuntimeException('Report Sending Builder[sendEmail]: No user with id ' . $userId);
|
|
}
|
|
|
|
$emailAddress = $user->get('emailAddress');
|
|
|
|
if (!$emailAddress) {
|
|
throw new RuntimeException('Report Sending Builder[sendEmail]: User has no email address');
|
|
}
|
|
|
|
/** @var Email $email */
|
|
$email = $this->entityManager->getNewEntity(Email::ENTITY_TYPE);
|
|
|
|
$email->set([
|
|
'to' => $emailAddress,
|
|
'subject' => $emailSubject,
|
|
'body' => $emailBody,
|
|
'isHtml' => true,
|
|
]);
|
|
|
|
$attachmentList = [];
|
|
|
|
if ($attachmentId) {
|
|
$attachment = $this->entityManager->getEntityById(Attachment::ENTITY_TYPE, $attachmentId);
|
|
|
|
if ($attachment) {
|
|
$attachmentList[] = $attachment;
|
|
}
|
|
}
|
|
|
|
try {
|
|
$this->emailSender
|
|
->create()
|
|
->withAttachments($attachmentList)
|
|
->send($email);
|
|
}
|
|
catch (Exception $e) {
|
|
if (isset($attachment)) {
|
|
$this->entityManager->removeEntity($attachment);
|
|
}
|
|
|
|
throw new RuntimeException("Report Email Sending:" . $e->getMessage());
|
|
}
|
|
|
|
if (isset($attachment)) {
|
|
$this->entityManager->removeEntity($attachment);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string|numeric $value
|
|
*/
|
|
private function formatCurrency($value, ?string $currency = null, ?int $decimalPlaces = null): string
|
|
{
|
|
if ($value === "") {
|
|
return $value;
|
|
}
|
|
|
|
$userThousandSeparator = $this->getPreference('thousandSeparator');
|
|
$userDecimalMark = $this->getPreference('decimalMark');
|
|
|
|
// @todo Revise.
|
|
$currencyFormat = (int) $this->config->get('currencyFormat');
|
|
|
|
if (!$currency) {
|
|
// @todo Support currency defined in the report result.
|
|
$currency = $this->config->get('defaultCurrency');
|
|
}
|
|
|
|
if ($currencyFormat) {
|
|
$pad = $decimalPlaces ?? ((int) $this->config->get('currencyDecimalPlaces'));
|
|
|
|
$value = number_format(floatval($value), $pad, $userDecimalMark, $userThousandSeparator);
|
|
} else {
|
|
$value = $this->formatFloat($value, $decimalPlaces);
|
|
}
|
|
|
|
switch ($currencyFormat) {
|
|
case 1:
|
|
$value = $value . ' ' . $currency;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
$currencySign = $this->metadata->get(['app', 'currency', 'symbolMap', $currency]);
|
|
|
|
$value = $currencySign . $value;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
$currencySign = $this->metadata->get(['app', 'currency', 'symbolMap', $currency]);
|
|
|
|
$value = $value . ' ' . $currencySign;
|
|
|
|
break;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* @param string|int $value
|
|
*/
|
|
private function formatInt($value): string
|
|
{
|
|
if ($value === '') {
|
|
return $value;
|
|
}
|
|
|
|
$userThousandSeparator = $this->getPreference('thousandSeparator');
|
|
$userDecimalMark = $this->getPreference('decimalMark');
|
|
|
|
return number_format(floatval($value), 0, $userDecimalMark, $userThousandSeparator);
|
|
}
|
|
|
|
/**
|
|
* @param string|float|int $value
|
|
*/
|
|
private function formatFloat($value, ?int $decimalPlaces = null): string
|
|
{
|
|
if ($value === '') {
|
|
return $value;
|
|
}
|
|
|
|
if ($value === 0 || $value === 0.0) {
|
|
return '0';
|
|
}
|
|
|
|
$userThousandSeparator = $this->getPreference('thousandSeparator');
|
|
$userDecimalMark = $this->getPreference('decimalMark');
|
|
|
|
$valueString = number_format(floatval($value), $decimalPlaces ?? 8, $userDecimalMark, $userThousandSeparator);
|
|
|
|
if ($decimalPlaces !== null) {
|
|
return $valueString;
|
|
}
|
|
|
|
return rtrim(rtrim($valueString, '0'), $userDecimalMark);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
*/
|
|
private function renderReport(
|
|
Report $entity,
|
|
string $templateName,
|
|
string $type,
|
|
array $data = []
|
|
): string {
|
|
|
|
$template = $this->templateFileManager->getTemplate($templateName, $type, null, 'Advanced');
|
|
$template = str_replace(["\n", "\r"], '', $template);
|
|
|
|
return $this->templateRendererFactory
|
|
->create()
|
|
->setTemplate($template)
|
|
->setEntity($entity)
|
|
->setSkipInlineAttachmentHandling()
|
|
->setData($data)
|
|
->render();
|
|
}
|
|
|
|
private function handleDateGroupValue(string $function, string $value): string
|
|
{
|
|
if ($function === 'MONTH') {
|
|
[$year, $month] = explode('-', $value);
|
|
|
|
$monthNamesShort = $this->language->get('Global.lists.monthNamesShort');
|
|
$monthLabel = $monthNamesShort[intval($month) - 1];
|
|
|
|
return $monthLabel . ' ' . $year;
|
|
}
|
|
|
|
if ($function === 'DAY') {
|
|
return $this->dateTime->convertSystemDate($value);
|
|
}
|
|
|
|
if ($function === 'QUARTER') {
|
|
return $value;
|
|
}
|
|
|
|
if ($function === 'YEAR_FISCAL') {
|
|
return $value . '-' . ((string) ((int) $value + 1));
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed>[][] $rows
|
|
* @throws Error
|
|
*/
|
|
private function prepareGrid1Result(array $rows, Report $report, stdClass $data): void
|
|
{
|
|
$bodyData = [
|
|
'rowList' => $rows,
|
|
];
|
|
|
|
try {
|
|
$subject = $this->renderReport($report, 'reportSendingGrid1', 'subject');
|
|
$body = $this->renderReport($report, 'reportSendingGrid1', 'body', $bodyData);
|
|
} catch (Exception $e) {
|
|
$this->log->error($e->getMessage());
|
|
|
|
throw Error::createWithBody(
|
|
'emailTemplateParsingError',
|
|
Error\Body::create()
|
|
->withMessageTranslation('emailTemplateParsingError', 'Report',
|
|
['template' => 'reportSendingGrid1'])
|
|
->encode()
|
|
);
|
|
}
|
|
|
|
$data->emailSubject = $subject;
|
|
$data->emailBody = $body;
|
|
$data->tableData = $rows;
|
|
}
|
|
|
|
/**
|
|
* @param GridResult $reportResult
|
|
*/
|
|
private function prepareGroupLabel(GridResult $reportResult, string $groupName, string $group): string
|
|
{
|
|
$label = $group;
|
|
|
|
if (empty($label)) {
|
|
$label = $this->language->translateLabel('-Empty-', 'labels', 'Report');
|
|
} else if (!empty($reportResult->getGroupValueMap()[$groupName][$group])) {
|
|
$label = $reportResult->getGroupValueMap()[$groupName][$group];
|
|
}
|
|
|
|
if (strpos($groupName, ':')) {
|
|
[$function,] = explode(':', $groupName);
|
|
|
|
$label = $this->handleDateGroupValue($function, $label);
|
|
}
|
|
|
|
return $label;
|
|
}
|
|
|
|
/**
|
|
* @param GridResult $reportResult
|
|
* @return array{string, string}
|
|
*/
|
|
private function prepareColumnDisplayData(
|
|
GridResult $reportResult,
|
|
string $column,
|
|
mixed $value,
|
|
): array {
|
|
|
|
$cDat = $this->gridHelper->getDataFromColumnName(
|
|
entityType: $reportResult->getEntityType(),
|
|
column: $column,
|
|
result: $reportResult,
|
|
);
|
|
|
|
/** @var array<string, int> $columnDecimalPlacesMap */
|
|
$columnDecimalPlacesMap = get_object_vars($reportResult->getColumnDecimalPlacesMap());
|
|
|
|
$align = 'left';
|
|
|
|
switch ($cDat->fieldType) {
|
|
case 'int':
|
|
$value = $this->formatInt($value);
|
|
$align = 'right';
|
|
|
|
break;
|
|
|
|
case 'float':
|
|
case 'decimal':
|
|
$decimalPlace = $columnDecimalPlacesMap[$column] ?? null;
|
|
|
|
$value = $this->formatFloat($value, $decimalPlace);
|
|
$align = 'right';
|
|
|
|
break;
|
|
|
|
case 'currency':
|
|
case 'currencyConverted':
|
|
$decimalPlace = $columnDecimalPlacesMap[$column] ?? null;
|
|
|
|
$value = $this->formatCurrency($value, null, $decimalPlace);
|
|
$align = 'right';
|
|
|
|
break;
|
|
}
|
|
|
|
return [$value, $align];
|
|
}
|
|
|
|
private function getNumericColumnType(string $column, Report $report): string
|
|
{
|
|
$columnType = 'int';
|
|
|
|
if (strpos($column, ':')) {
|
|
[$f, $field] = explode(':', $column);
|
|
|
|
if ($f !== 'COUNT') {
|
|
$columnType = $this->metadata
|
|
->get(['entityDefs', $report->getTargetEntityType(), 'fields', $field, 'type']);
|
|
}
|
|
}
|
|
|
|
if ($columnType === 'float') {
|
|
// @todo Refactor.
|
|
$view = $this->metadata
|
|
->get(['entityDefs', $report->getTargetEntityType(), 'fields', $field ?? $column, 'view']);
|
|
|
|
if ($view && strpos($view, 'currency-converted')) {
|
|
$columnType = 'currencyConverted';
|
|
}
|
|
}
|
|
|
|
$allowedTypeList = [
|
|
'int',
|
|
'float',
|
|
'decimal',
|
|
'currency',
|
|
'currencyConverted',
|
|
];
|
|
|
|
if (!in_array($columnType, $allowedTypeList)) {
|
|
$columnType = 'int';
|
|
}
|
|
|
|
return $columnType;
|
|
}
|
|
}
|