updated advanced pack to 3.12.0:
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.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<?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\Tools\Report\Export;
|
||||
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Helper;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Result as GridResult;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
class CellValueHelper
|
||||
{
|
||||
public function __construct(
|
||||
private Helper $gridHelper,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Only for Grid-2.
|
||||
*/
|
||||
public function getCellDisplayValueFromResult(
|
||||
int $groupIndex,
|
||||
string $groupValue,
|
||||
string $column,
|
||||
GridResult $reportResult,
|
||||
): mixed {
|
||||
|
||||
$groupName = $reportResult->getGroupByList()[$groupIndex];
|
||||
|
||||
$dataMap = $reportResult->getNonSummaryData()->$groupName ?? null;
|
||||
|
||||
if (!$dataMap instanceof stdClass) {
|
||||
throw new RuntimeException("No non-summary data for the group '$groupName'.");
|
||||
}
|
||||
|
||||
$value = '';
|
||||
|
||||
if ($this->gridHelper->isColumnNumeric($column, $reportResult)) {
|
||||
$value = 0;
|
||||
}
|
||||
|
||||
if (
|
||||
property_exists($dataMap, $groupValue) &&
|
||||
property_exists($dataMap->$groupValue, $column)
|
||||
) {
|
||||
$value = $dataMap->$groupValue->$column;
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->gridHelper->isColumnNumeric($column, $reportResult) &&
|
||||
!is_null($value)
|
||||
) {
|
||||
if (property_exists($reportResult->getCellValueMaps(), $column)) {
|
||||
if (property_exists($reportResult->getCellValueMaps()->$column, $value)) {
|
||||
$value = $reportResult->getCellValueMaps()->$column->$value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($value)) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
1454
custom/Espo/Modules/Advanced/Tools/Report/Export/ExportXlsx.php
Normal file
1454
custom/Espo/Modules/Advanced/Tools/Report/Export/ExportXlsx.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,310 @@
|
||||
<?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\Tools\Report\Export;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\CellFunction;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\CellType;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\DataCell;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\DataRow;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\DateFunction;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\RowType;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\SheetData;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Result;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
class Grid1DataBuilder
|
||||
{
|
||||
private const PRECISION = 2;
|
||||
|
||||
public function __construct(
|
||||
private Language $language,
|
||||
) {}
|
||||
|
||||
public function build(Result $reportResult): SheetData
|
||||
{
|
||||
$hasSubListColumns = $reportResult->getSubListColumnList() !== [];
|
||||
$groupCount = count($reportResult->getGroupByList());
|
||||
$groupName = $reportResult->getGroupByList()[0] ?? '__STUB__';
|
||||
|
||||
$rows = [];
|
||||
|
||||
$cells = [
|
||||
new DataCell(
|
||||
value: $reportResult->getGroupNameMap()[$groupName] ?? null,
|
||||
type: CellType::HeadGroup,
|
||||
),
|
||||
];
|
||||
|
||||
foreach ($reportResult->getColumnList() as $column) {
|
||||
$cells[] = new DataCell(
|
||||
value: $reportResult->getColumnNameMap()[$column] ?? null,
|
||||
type: $this->isNonSummary($reportResult, $column) ? CellType::HeadNonSummary : CellType::HeadSummary,
|
||||
);
|
||||
}
|
||||
|
||||
$rows[] = new DataRow(
|
||||
cells: $cells,
|
||||
type: RowType::Header,
|
||||
);
|
||||
|
||||
foreach ($reportResult->getGrouping()[0] ?? [] as $group) {
|
||||
$itemRows = $this->buildGroupRows($reportResult, $groupName, $group);
|
||||
|
||||
$rows = [...$rows, ...$itemRows];
|
||||
}
|
||||
|
||||
if ($groupCount) {
|
||||
$cells = [];
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $this->language->translateLabel('Total', 'labels', Report::ENTITY_TYPE),
|
||||
type: CellType::Label,
|
||||
);
|
||||
|
||||
foreach ($reportResult->getColumnList() as $column) {
|
||||
if (
|
||||
!in_array($column, $reportResult->getNumericColumnList()) ||
|
||||
!in_array($column, $reportResult->getAggregatedColumnList())
|
||||
) {
|
||||
$cells[] = new DataCell(
|
||||
value: null,
|
||||
type: CellType::Empty,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$decimalPlaces = $reportResult->getColumnDecimalPlacesMap()->$column ?? null;
|
||||
$fieldType = $reportResult->getColumnTypeMap()[$column] ?? null;
|
||||
$function = Grid2NormalizedDataBuilder::getCellFunction($column);
|
||||
|
||||
if ($fieldType === FieldType::INT && $function === CellFunction::Avg) {
|
||||
$decimalPlaces = self::PRECISION;
|
||||
}
|
||||
|
||||
$value = $reportResult->getSums()->$column ?? 0;
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $value,
|
||||
type: CellType::Total,
|
||||
fieldType: $fieldType,
|
||||
function: !$hasSubListColumns ? $function : null,
|
||||
decimalPlaces: $decimalPlaces,
|
||||
);
|
||||
}
|
||||
|
||||
$rows[] = new DataRow(
|
||||
cells: $cells,
|
||||
type: RowType::Total,
|
||||
);
|
||||
}
|
||||
|
||||
$hasTotalFunctions = !$hasSubListColumns && $groupCount;
|
||||
|
||||
return new SheetData(
|
||||
rows: $rows,
|
||||
firstSummaryRowNumber: $hasTotalFunctions ? 1 : null,
|
||||
lastSummaryRowNumber: $hasTotalFunctions ? count($rows) - 2 : null,
|
||||
);
|
||||
}
|
||||
|
||||
private function isNonSummary(Result $reportResult, string $column): bool
|
||||
{
|
||||
return in_array($column, $reportResult->getNonSummaryColumnList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataRow[]
|
||||
*/
|
||||
private function buildGroupRows(Result $reportResult, string $groupName, mixed $group): array
|
||||
{
|
||||
$hasSubListColumns = $reportResult->getSubListColumnList() !== [];
|
||||
|
||||
$rows = [];
|
||||
|
||||
if (!$hasSubListColumns) {
|
||||
return [
|
||||
$this->buildGroupTotalRow($reportResult, $groupName, $group, true, true)
|
||||
];
|
||||
}
|
||||
|
||||
$rows[] = $this->buildGroupTotalRow($reportResult, $groupName, $group, false);
|
||||
|
||||
foreach ($this->buildSubRows($reportResult, $group) as $row) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$rows[] = $this->buildGroupTotalRow($reportResult, $groupName, $group, true);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function buildGroupTotalRow(
|
||||
Result $reportResult,
|
||||
string $groupName,
|
||||
mixed $group,
|
||||
bool $onlyNumeric,
|
||||
bool $full = false,
|
||||
): DataRow {
|
||||
|
||||
$hasSubListColumns = $reportResult->getSubListColumnList() !== [];
|
||||
|
||||
$cells = [];
|
||||
|
||||
if (!$onlyNumeric || $full) {
|
||||
$cells[] = new DataCell(
|
||||
value: $this->prepareGroupValue($reportResult, $groupName, $group),
|
||||
type: CellType::NonSummary,
|
||||
dateFunction: $this->getDateFunction($groupName),
|
||||
);
|
||||
} else {
|
||||
$cells[] = new DataCell(
|
||||
value: $this->language->translateLabel('Group Total', 'labels', 'Report'),
|
||||
type: CellType::Label,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($reportResult->getColumnList() as $column) {
|
||||
$isNumericValue = in_array($column, $reportResult->getNumericColumnList());
|
||||
|
||||
if (
|
||||
$hasSubListColumns && !$onlyNumeric && $isNumericValue ||
|
||||
$hasSubListColumns && $onlyNumeric && !$isNumericValue ||
|
||||
$isNumericValue && !in_array($column, $reportResult->getAggregatedColumnList())
|
||||
) {
|
||||
$cells[] = new DataCell(
|
||||
value: null,
|
||||
type: CellType::Empty,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($isNumericValue) {
|
||||
$value = $reportResult->getReportData()->$group->$column ?? 0;
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $value,
|
||||
type: CellType::Summary,
|
||||
fieldType: $reportResult->getColumnTypeMap()[$column] ?? null,
|
||||
decimalPlaces: $reportResult->getColumnDecimalPlacesMap()->$column ?? null,
|
||||
);
|
||||
} else {
|
||||
$value = $reportResult->getReportData()->$group->$column ?? null;
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $value,
|
||||
type: CellType::Summary,
|
||||
fieldType: $reportResult->getColumnTypeMap()[$column] ?? null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new DataRow(
|
||||
cells: $cells,
|
||||
type: $full || $onlyNumeric ? RowType::DataRow : RowType::HeadDataRow,
|
||||
);
|
||||
}
|
||||
|
||||
private function prepareGroupValue(Result $reportResult, string $groupName, mixed $group): mixed
|
||||
{
|
||||
if ($group) {
|
||||
$label = $reportResult->getGroupValueMap()[$groupName][$group] ?? $group;
|
||||
} else {
|
||||
$label = $this->language->translateLabel('-Empty-', 'labels', 'Report');
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
private function getDateFunction(string $column): ?DateFunction
|
||||
{
|
||||
return Grid2NormalizedDataBuilder::getDateFunction($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataRow[]
|
||||
*/
|
||||
private function buildSubRows(Result $reportResult, mixed $group): array
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
foreach ($reportResult->getSubListData()->$group ?? [] as $item) {
|
||||
if (!$item instanceof stdClass) {
|
||||
throw new RuntimeException("Bad sub-list item.");
|
||||
}
|
||||
$rows[] = $this->buildSubRowItem($reportResult, $group, $item);
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function buildSubRowItem(Result $reportResult, mixed $group, stdClass $item): DataRow
|
||||
{
|
||||
$cells = [];
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: null,
|
||||
type: CellType::Empty,
|
||||
);
|
||||
|
||||
foreach ($reportResult->getColumnList() as $column) {
|
||||
if (!in_array($column, $reportResult->getSubListColumnList())) {
|
||||
$cells[] = new DataCell(
|
||||
value: null,
|
||||
type: CellType::Empty,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$isNumericValue = in_array($column, $reportResult->getNumericColumnList());
|
||||
|
||||
if ($isNumericValue) {
|
||||
$value = $item->$column ?? 0;
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $value,
|
||||
type: CellType::NonSummary,
|
||||
fieldType: $reportResult->getColumnTypeMap()[$column] ?? null,
|
||||
decimalPlaces: $reportResult->getColumnDecimalPlacesMap()->$column ?? null,
|
||||
isNumeric: true,
|
||||
);
|
||||
} else {
|
||||
$value = $item->$column ?? null;
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $value,
|
||||
type: CellType::NonSummary,
|
||||
fieldType: $reportResult->getColumnTypeMap()[$column] ?? null,
|
||||
isNumeric: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new DataRow(
|
||||
cells: $cells,
|
||||
type: RowType::SubDataRow,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?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\Tools\Report\Export;
|
||||
|
||||
use Espo\Core\ORM\Type\FieldType;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\CellFunction;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\CellType;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\DataCell;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\DataRow;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\DateFunction;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\SheetData;
|
||||
use Espo\Modules\Advanced\Tools\Report\Export\Xlsx\RowType;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Result;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Result as GridResult;
|
||||
|
||||
class Grid2NormalizedDataBuilder
|
||||
{
|
||||
private const PRECISION = 2;
|
||||
|
||||
public function __construct(
|
||||
private Language $language,
|
||||
private CellValueHelper $cellValueHelper,
|
||||
) {}
|
||||
|
||||
public function build(Result $reportResult): SheetData
|
||||
{
|
||||
$groupName1 = $reportResult->getGroupByList()[0];
|
||||
$groupName2 = $reportResult->getGroupByList()[1];
|
||||
|
||||
$rows = [];
|
||||
|
||||
$cells = [
|
||||
new DataCell(
|
||||
value: $reportResult->getGroupNameMap()[$groupName1] ?? null,
|
||||
type: CellType::HeadGroup,
|
||||
),
|
||||
new DataCell(
|
||||
value: $reportResult->getGroupNameMap()[$groupName2] ?? null,
|
||||
type: CellType::HeadGroup,
|
||||
),
|
||||
];
|
||||
|
||||
foreach ($reportResult->getNonSummaryColumnList() as $column) {
|
||||
$cells[] = new DataCell(
|
||||
value: $reportResult->getColumnNameMap()[$column] ?? null,
|
||||
type: CellType::HeadNonSummary,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($reportResult->getSummaryColumnList() as $column) {
|
||||
$cells[] = new DataCell(
|
||||
value: $reportResult->getColumnNameMap()[$column] ?? null,
|
||||
type: CellType::HeadSummary,
|
||||
);
|
||||
}
|
||||
|
||||
$rows[] = new DataRow(
|
||||
cells: $cells,
|
||||
type: RowType::Header,
|
||||
);
|
||||
|
||||
foreach ($reportResult->getGrouping()[0] ?? [] as $group) {
|
||||
foreach ($reportResult->getGrouping()[1] ?? [] as $secondGroup) {
|
||||
$cells = [];
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $this->prepareGroupValue($reportResult, $groupName1, $group),
|
||||
type: CellType::NonSummary,
|
||||
dateFunction: self::getDateFunction($groupName1),
|
||||
);
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $this->prepareGroupValue($reportResult, $groupName2, $secondGroup),
|
||||
type: CellType::NonSummary,
|
||||
dateFunction: self::getDateFunction($groupName2),
|
||||
);
|
||||
|
||||
foreach ($reportResult->getNonSummaryColumnList() as $column) {
|
||||
$columnGroup = $reportResult->getNonSummaryColumnGroupMap()->$column ?? null;
|
||||
|
||||
$columnGroupValue = $columnGroup === $reportResult->getGroupByList()[0] ?
|
||||
$group : $secondGroup;
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $this->getCellDisplayValueFromResult(
|
||||
groupIndex: $columnGroup === $reportResult->getGroupByList()[0] ? 0 : 1,
|
||||
groupValue: $columnGroupValue,
|
||||
column: $column,
|
||||
reportResult: $reportResult,
|
||||
),
|
||||
type: CellType::NonSummary,
|
||||
fieldType: $reportResult->getColumnTypeMap()[$column] ?? null,
|
||||
decimalPlaces: $reportResult->getColumnDecimalPlacesMap()->$column ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
$hasNonEmpty = false;
|
||||
|
||||
foreach ($reportResult->getSummaryColumnList() as $column) {
|
||||
$value = $reportResult->getReportData()->$group->$secondGroup->$column ?? null;
|
||||
|
||||
if ($value !== null) {
|
||||
$hasNonEmpty = true;
|
||||
}
|
||||
|
||||
$decimalPlaces = $reportResult->getColumnDecimalPlacesMap()->$column ?? null;
|
||||
$fieldType = $reportResult->getColumnTypeMap()[$column] ?? null;
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $value,
|
||||
type: CellType::Summary,
|
||||
fieldType: $fieldType,
|
||||
decimalPlaces: $decimalPlaces,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$hasNonEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows[] = new DataRow(
|
||||
cells: $cells,
|
||||
type: RowType::DataRow,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$cells = [];
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $this->language->translateLabel('Total', 'labels', Report::ENTITY_TYPE),
|
||||
type: CellType::Label,
|
||||
);
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: null,
|
||||
type: CellType::Empty,
|
||||
);
|
||||
|
||||
foreach ($reportResult->getNonSummaryColumnList() as $ignored) {
|
||||
$cells[] = new DataCell(
|
||||
value: null,
|
||||
type: CellType::Empty,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($reportResult->getSummaryColumnList() as $column) {
|
||||
$value = $reportResult->getSums()->$column ?? 0;
|
||||
|
||||
$decimalPlaces = $reportResult->getColumnDecimalPlacesMap()->$column ?? null;
|
||||
$fieldType = $reportResult->getColumnTypeMap()[$column] ?? null;
|
||||
$function = self::getCellFunction($column);
|
||||
|
||||
if ($fieldType === FieldType::INT && $function === CellFunction::Avg) {
|
||||
$decimalPlaces = self::PRECISION;
|
||||
}
|
||||
|
||||
$cells[] = new DataCell(
|
||||
value: $value,
|
||||
type: CellType::Total,
|
||||
fieldType: $fieldType,
|
||||
function: $function,
|
||||
decimalPlaces: $decimalPlaces,
|
||||
);
|
||||
}
|
||||
|
||||
$rows[] = new DataRow(
|
||||
cells: $cells,
|
||||
type: RowType::Total,
|
||||
);
|
||||
|
||||
return new SheetData(
|
||||
rows: $rows,
|
||||
firstSummaryRowNumber: 1,
|
||||
lastSummaryRowNumber: count($rows) - 2,
|
||||
);
|
||||
}
|
||||
|
||||
private function getCellDisplayValueFromResult(
|
||||
int $groupIndex,
|
||||
string $groupValue,
|
||||
string $column,
|
||||
GridResult $reportResult,
|
||||
): mixed {
|
||||
|
||||
return $this->cellValueHelper->getCellDisplayValueFromResult(
|
||||
groupIndex: $groupIndex,
|
||||
groupValue: $groupValue,
|
||||
column: $column,
|
||||
reportResult: $reportResult,
|
||||
);
|
||||
}
|
||||
|
||||
private function prepareGroupValue(Result $reportResult, string $groupName, mixed $group): string
|
||||
{
|
||||
if (!$group) {
|
||||
return $this->language->translateLabel('-Empty-', 'labels', 'Report');
|
||||
}
|
||||
|
||||
if (isset($reportResult->getGroupValueMap()[$groupName][$group])) {
|
||||
return $reportResult->getGroupValueMap()[$groupName][$group];
|
||||
}
|
||||
|
||||
return (string) $group;
|
||||
}
|
||||
|
||||
public static function getCellFunction(string $column): ?CellFunction
|
||||
{
|
||||
[$function] = explode(':', $column);
|
||||
|
||||
if ($function === 'COUNT') {
|
||||
return CellFunction::Sum;
|
||||
}
|
||||
|
||||
if ($function === 'SUM') {
|
||||
return CellFunction::Sum;
|
||||
}
|
||||
|
||||
if ($function === 'AVG') {
|
||||
return CellFunction::Avg;
|
||||
}
|
||||
|
||||
if ($function === 'MIN') {
|
||||
return CellFunction::Min;
|
||||
}
|
||||
|
||||
if ($function === 'MAX') {
|
||||
return CellFunction::Max;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getDateFunction(string $column): ?DateFunction
|
||||
{
|
||||
if (!str_contains($column, ':')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$f,] = explode(':', $column);
|
||||
|
||||
return match ($f) {
|
||||
'MONTH' => DateFunction::Month,
|
||||
'YEAR' => DateFunction::Year,
|
||||
'DAY' => DateFunction::Day,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
<?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\Tools\Report\Export;
|
||||
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Field\LinkParent;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Select\Where\Item as WhereItem;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\Attachment;
|
||||
use Espo\Entities\Template;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Data as GridTypeData;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Helper as GridHelper;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Result as GridResult;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Util as GridUtil;
|
||||
use Espo\Modules\Advanced\Tools\Report\Service;
|
||||
use Espo\Modules\Crm\Entities\Opportunity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Pdf\Data;
|
||||
use Espo\Tools\Pdf\Service as PdfService;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use RuntimeException;
|
||||
|
||||
class GridExportService
|
||||
{
|
||||
private const STUB_KEY = '__STUB__';
|
||||
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private AclManager $aclManager,
|
||||
private Metadata $metadata,
|
||||
private Config $config,
|
||||
private Language $language,
|
||||
private Service $service,
|
||||
private GridHelper $gridHelper,
|
||||
private GridUtil $gridUtil,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private PdfService $pdfService,
|
||||
private Grid2NormalizedDataBuilder $grid2NormalizedDataBuilder,
|
||||
private Grid1DataBuilder $grid1DataBuilder,
|
||||
private CellValueHelper $cellValueHelper,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function exportXlsx(string $id, ?WhereItem $where, ?User $user = null): string
|
||||
{
|
||||
$report = $this->entityManager->getRDBRepositoryByClass(Report::class)->getById($id);
|
||||
|
||||
if (!$report) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$this->checkAccess($report, $user);
|
||||
|
||||
$contents = $this->buildXlsxContents($id, $where, $user);
|
||||
|
||||
$mimeType = $this->metadata->get(['app', 'export', 'formatDefs', 'xlsx', 'mimeType']);
|
||||
|
||||
$fileName = $this->prepareXlsxFileName($report);
|
||||
|
||||
$attachment = $this->entityManager->getRDBRepositoryByClass(Attachment::class)->getNew();
|
||||
|
||||
$attachment
|
||||
->setName($fileName)
|
||||
->setRole(Attachment::ROLE_EXPORT_FILE)
|
||||
->setType($mimeType)
|
||||
->setContents($contents)
|
||||
->setRelated(LinkParent::createFromEntity($report));
|
||||
|
||||
$this->entityManager->saveEntity($attachment);
|
||||
|
||||
return $attachment->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function buildXlsxContents(string $id, ?WhereItem $where, ?User $user = null): string
|
||||
{
|
||||
$report = $this->entityManager->getRDBRepositoryByClass(Report::class)->getById($id);
|
||||
|
||||
if (!$report) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$entityType = $report->getTargetEntityType();
|
||||
|
||||
$groupCount = count($report->getGroupBy());
|
||||
|
||||
$columnList = $report->getColumns();
|
||||
$groupByList = $report->getGroupBy();
|
||||
|
||||
$reportResult = null;
|
||||
|
||||
if (
|
||||
$report->getType() === Report::TYPE_JOINT_GRID ||
|
||||
!$report->getGroupBy()
|
||||
) {
|
||||
$reportResult = $this->service->runGrid($id, $where, $user);
|
||||
|
||||
$columnList = $reportResult->getColumnList();
|
||||
$groupByList = $reportResult->getGroupByList();
|
||||
$groupCount = count($groupByList);
|
||||
}
|
||||
|
||||
$reportResult ??= $this->service->runGrid($id, $where, $user);
|
||||
|
||||
$result = [];
|
||||
$sheetData = null;
|
||||
|
||||
if ($groupCount === 2 && $reportResult->getTableMode() !== GridTypeData::TABLE_MODE_NORMALIZED) {
|
||||
foreach ($reportResult->getSummaryColumnList() as $column) {
|
||||
$result[] = $this->getGrid2ResultForExport($reportResult, $column);
|
||||
}
|
||||
} else if ($groupCount === 2 && $reportResult->getTableMode() === GridTypeData::TABLE_MODE_NORMALIZED) {
|
||||
$sheetData = $this->grid2NormalizedDataBuilder->build($reportResult);
|
||||
} else if ($groupCount === 1 && $reportResult->getSubListColumnList()) {
|
||||
$sheetData = $this->grid1DataBuilder->build($reportResult);
|
||||
} else {
|
||||
$result[] = $this->getGrid1ResultForExport($reportResult);
|
||||
}
|
||||
|
||||
$columnTypes = [];
|
||||
|
||||
foreach ($columnList as $item) {
|
||||
$columnData = $this->gridHelper->getDataFromColumnName($entityType, $item, $reportResult);
|
||||
|
||||
$type = $this->metadata
|
||||
->get(['entityDefs', $columnData->entityType, 'fields', $columnData->field, 'type']);
|
||||
|
||||
if (
|
||||
$entityType === Opportunity::ENTITY_TYPE &&
|
||||
$columnData->field === 'amountWeightedConverted'
|
||||
) {
|
||||
$type = 'currencyConverted';
|
||||
}
|
||||
|
||||
if ($columnData->function === 'COUNT') {
|
||||
$type = 'int';
|
||||
}
|
||||
|
||||
$columnTypes[$item] = $type;
|
||||
}
|
||||
|
||||
$columnLabels = [];
|
||||
|
||||
if ($groupCount === 2) {
|
||||
$columnNameMap = $reportResult->getColumnNameMap();
|
||||
|
||||
foreach ($columnList as $column) {
|
||||
$columnLabels[$column] = $columnNameMap[$column] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
$exportParams = [
|
||||
'exportName' => $report->getName(),
|
||||
'columnList' => $columnList,
|
||||
'columnTypes' => $columnTypes,
|
||||
'chartType' => $reportResult->getChartType() ?? $report->getChartType(),
|
||||
'groupByList' => $groupByList,
|
||||
'columnLabels' => $columnLabels,
|
||||
'reportResult' => $reportResult,
|
||||
'groupLabel' => '',
|
||||
'currency' => $reportResult->getCurrency(),
|
||||
];
|
||||
|
||||
if ($groupCount) {
|
||||
$group = $groupByList[$groupCount - 1];
|
||||
$exportParams['groupLabel'] = $reportResult->getGroupNameMap()[$group] ??
|
||||
$this->gridUtil->translateGroupName($entityType, $group);
|
||||
}
|
||||
|
||||
$export = $this->injectableFactory->create(ExportXlsx::class);
|
||||
|
||||
if ($sheetData) {
|
||||
try {
|
||||
return $export->processWithSheedData($reportResult, $sheetData, $report);
|
||||
} catch (PhpSpreadsheetException $e) {
|
||||
throw new RuntimeException($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $export->process($entityType, $exportParams, $result);
|
||||
} catch (PhpSpreadsheetException $e) {
|
||||
throw new RuntimeException($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function exportCsv(
|
||||
string $id,
|
||||
?WhereItem $where,
|
||||
?string $column = null,
|
||||
?User $user = null,
|
||||
): string {
|
||||
|
||||
$report = $this->entityManager->getRDBRepositoryByClass(Report::class)->getById($id);
|
||||
|
||||
if (!$report) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$this->checkAccess($report, $user);
|
||||
|
||||
$contents = $this->getGridReportCsv($id, $where, $column, $user);
|
||||
|
||||
$mimeType = $this->metadata->get(['app', 'export', 'formatDefs', 'csv', 'mimeType']);
|
||||
|
||||
$fileName = $this->prepareCsvFileName($report);
|
||||
|
||||
$attachment = $this->entityManager->getRDBRepositoryByClass(Attachment::class)->getNew();
|
||||
|
||||
$attachment
|
||||
->setName($fileName)
|
||||
->setRole(Attachment::ROLE_EXPORT_FILE)
|
||||
->setType($mimeType)
|
||||
->setContents($contents)
|
||||
->setRelated(LinkParent::createFromEntity($report));
|
||||
|
||||
$this->entityManager->saveEntity($attachment);
|
||||
|
||||
return $attachment->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function getGridReportCsv(
|
||||
string $id,
|
||||
?WhereItem $where,
|
||||
?string $column = null,
|
||||
?User $user = null,
|
||||
): string {
|
||||
|
||||
$result = $this->getGridReportResultForExport(
|
||||
id: $id,
|
||||
where: $where,
|
||||
currentColumn: $column,
|
||||
user: $user,
|
||||
);
|
||||
|
||||
$delimiter = $this->config->get('exportDelimiter', ';');
|
||||
|
||||
$fp = fopen('php://temp', 'w');
|
||||
|
||||
if ($fp === false) {
|
||||
throw new RuntimeException("Could not open temp.");
|
||||
}
|
||||
|
||||
foreach ($result as $row) {
|
||||
fputcsv($fp, $row, $delimiter);
|
||||
}
|
||||
|
||||
rewind($fp);
|
||||
$csv = stream_get_contents($fp);
|
||||
fclose($fp);
|
||||
|
||||
if ($csv === false) {
|
||||
throw new RuntimeException("Could not get from stream.");
|
||||
}
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, mixed>[]
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private function getGridReportResultForExport(
|
||||
string $id,
|
||||
?WhereItem $where,
|
||||
?string $currentColumn = null,
|
||||
?User $user = null,
|
||||
): array {
|
||||
|
||||
$reportResult = $this->service->runGrid($id, $where, $user);
|
||||
|
||||
$depth = count($reportResult->getGroupByList());
|
||||
|
||||
if ($depth === 2 && $reportResult->getTableMode() === GridTypeData::TABLE_MODE_NORMALIZED) {
|
||||
$sheetData = $this->grid2NormalizedDataBuilder->build($reportResult);
|
||||
|
||||
return $this->sheetDataToRaw($sheetData);
|
||||
}
|
||||
|
||||
if ($depth === 1 && $reportResult->getSubListColumnList()) {
|
||||
$sheetData = $this->grid1DataBuilder->build($reportResult);
|
||||
|
||||
return $this->sheetDataToRaw($sheetData);
|
||||
}
|
||||
|
||||
if ($depth === 2) {
|
||||
return $this->getGrid2ResultForExport($reportResult, $currentColumn);
|
||||
}
|
||||
|
||||
if ($depth === 1 || $depth === 0) {
|
||||
return $this->getGrid1ResultForExport($reportResult);
|
||||
}
|
||||
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public function getCellDisplayValueFromResult(
|
||||
int $groupIndex,
|
||||
string $groupValue,
|
||||
string $column,
|
||||
GridResult $reportResult,
|
||||
): mixed {
|
||||
|
||||
return $this->cellValueHelper->getCellDisplayValueFromResult(
|
||||
groupIndex: $groupIndex,
|
||||
groupValue: $groupValue,
|
||||
column: $column,
|
||||
reportResult: $reportResult,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function exportPdf(
|
||||
string $id,
|
||||
?WhereItem $where,
|
||||
string $templateId,
|
||||
?User $user = null,
|
||||
): string {
|
||||
|
||||
$report = $this->entityManager->getRDBRepositoryByClass(Report::class)->getById($id);
|
||||
|
||||
$template = $this->entityManager->getRDBRepositoryByClass(Template::class)->getById($templateId);
|
||||
|
||||
if (!$report || !$template) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
if (!$this->aclManager->checkEntityRead($user, $report)) {
|
||||
throw new Forbidden("No access to report.");
|
||||
}
|
||||
|
||||
if (!$this->aclManager->checkEntityRead($user, $template)) {
|
||||
throw new Forbidden("No access to template.");
|
||||
}
|
||||
}
|
||||
|
||||
$additionalData = [
|
||||
'user' => $user,
|
||||
'reportWhere' => $where,
|
||||
];
|
||||
|
||||
$contents = $this->pdfService
|
||||
->generate(
|
||||
Report::ENTITY_TYPE,
|
||||
$report->getId(),
|
||||
$template->getId(),
|
||||
null,
|
||||
Data::create()->withAdditionalTemplateData((object) $additionalData)
|
||||
)
|
||||
->getString();
|
||||
|
||||
$attachment = $this->entityManager->getRDBRepositoryByClass(Attachment::class)->getNew();
|
||||
|
||||
$attachment
|
||||
->setRole(Attachment::ROLE_EXPORT_FILE)
|
||||
->setType('application/pdf')
|
||||
->setContents($contents)
|
||||
->setRelated(LinkParent::createFromEntity($report));
|
||||
|
||||
$this->entityManager->saveEntity($attachment);
|
||||
|
||||
return $attachment->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, mixed>[]
|
||||
*/
|
||||
private function getGrid2ResultForExport(GridResult $reportResult, ?string $currentColumn): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$reportData = $reportResult->getReportData();
|
||||
|
||||
$groupName1 = $reportResult->getGroupByList()[0];
|
||||
$groupName2 = $reportResult->getGroupByList()[1];
|
||||
|
||||
$group1NonSummaryColumnList = [];
|
||||
$group2NonSummaryColumnList = [];
|
||||
|
||||
if ($reportResult->getGroup1NonSummaryColumnList() !== null) {
|
||||
$group1NonSummaryColumnList = $reportResult->getGroup1NonSummaryColumnList();
|
||||
}
|
||||
|
||||
if ($reportResult->getGroup2NonSummaryColumnList() !== null) {
|
||||
$group2NonSummaryColumnList = $reportResult->getGroup2NonSummaryColumnList();
|
||||
}
|
||||
|
||||
$row = [];
|
||||
|
||||
$row[] = '';
|
||||
|
||||
foreach ($group2NonSummaryColumnList as $column) {
|
||||
$text = $reportResult->getColumnNameMap()[$column];
|
||||
|
||||
$row[] = $text;
|
||||
}
|
||||
|
||||
foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) {
|
||||
$label = $gr1;
|
||||
|
||||
if (empty($label)) {
|
||||
$label = $this->language->translate('-Empty-', 'labels', 'Report');
|
||||
} else if (!empty($reportResult->getGroupValueMap()[$groupName1][$gr1])) {
|
||||
$label = $reportResult->getGroupValueMap()[$groupName1][$gr1];
|
||||
}
|
||||
|
||||
$row[] = $label;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
|
||||
foreach ($reportResult->getGrouping()[1] ?? [] as $gr2) {
|
||||
$row = [];
|
||||
$label = $gr2;
|
||||
|
||||
if (empty($label)) {
|
||||
$label = $this->language->translate('-Empty-', 'labels', 'Report');
|
||||
} else if (!empty($reportResult->getGroupValueMap()[$groupName2][$gr2])) {
|
||||
$label = $reportResult->getGroupValueMap()[$groupName2][$gr2];
|
||||
}
|
||||
|
||||
$row[] = $label;
|
||||
|
||||
foreach ($group2NonSummaryColumnList as $column) {
|
||||
$row[] = $this->getCellDisplayValueFromResult(1, $gr2, $column, $reportResult);
|
||||
}
|
||||
|
||||
foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) {
|
||||
$value = 0;
|
||||
|
||||
if (!empty($reportData->$gr1) && !empty($reportData->$gr1->$gr2)) {
|
||||
if (!empty($reportData->$gr1->$gr2->$currentColumn)) {
|
||||
$value = $reportData->$gr1->$gr2->$currentColumn;
|
||||
}
|
||||
}
|
||||
|
||||
$row[] = $value;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
$row = [];
|
||||
|
||||
$row[] = $this->language->translate('Total', 'labels', 'Report');
|
||||
|
||||
foreach ($group2NonSummaryColumnList as $ignored) {
|
||||
$row[] = '';
|
||||
}
|
||||
|
||||
foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) {
|
||||
$sum = 0;
|
||||
|
||||
if (!empty($reportResult->getGroup1Sums()->$gr1)) {
|
||||
if (!empty($reportResult->getGroup1Sums()->$gr1->$currentColumn)) {
|
||||
$sum = $reportResult->getGroup1Sums()->$gr1->$currentColumn;
|
||||
}
|
||||
}
|
||||
|
||||
$row[] = $sum;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
|
||||
if (count($group1NonSummaryColumnList)) {
|
||||
$result[] = [];
|
||||
}
|
||||
|
||||
foreach ($group1NonSummaryColumnList as $column) {
|
||||
$row = [];
|
||||
$text = $reportResult->getColumnNameMap()[$column];
|
||||
$row[] = $text;
|
||||
|
||||
foreach ($group2NonSummaryColumnList as $ignored) {
|
||||
$row[] = '';
|
||||
}
|
||||
|
||||
foreach ($reportResult->getGrouping()[0] ?? [] as $gr1) {
|
||||
$row[] = $this->getCellDisplayValueFromResult(0, $gr1, $column, $reportResult);
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, mixed>[]
|
||||
*/
|
||||
private function getGrid1ResultForExport(GridResult $reportResult): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$depth = count($reportResult->getGroupByList());
|
||||
$reportData = $reportResult->getReportData();
|
||||
|
||||
$aggregatedColumnList = $reportResult->getAggregatedColumnList();
|
||||
|
||||
if ($depth === 1) {
|
||||
$groupName = $reportResult->getGroupByList()[0];
|
||||
} else {
|
||||
$groupName = self::STUB_KEY;
|
||||
}
|
||||
|
||||
$row = [];
|
||||
$row[] = '';
|
||||
|
||||
foreach ($aggregatedColumnList as $column) {
|
||||
$label = $column;
|
||||
|
||||
if (!empty($reportResult->getColumnNameMap()[$column])) {
|
||||
$label = $reportResult->getColumnNameMap()[$column];
|
||||
}
|
||||
|
||||
$row[] = $label;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
|
||||
foreach ($reportResult->getGrouping()[0] ?? [] as $gr) {
|
||||
$row = [];
|
||||
|
||||
$label = $gr;
|
||||
|
||||
if (empty($label)) {
|
||||
$label = $this->language->translate('-Empty-', 'labels', 'Report');
|
||||
} else if (
|
||||
!empty($reportResult->getGroupValueMap()[$groupName]) &&
|
||||
array_key_exists($gr, $reportResult->getGroupValueMap()[$groupName])
|
||||
) {
|
||||
$label = $reportResult->getGroupValueMap()[$groupName][$gr];
|
||||
}
|
||||
|
||||
$row[] = $label;
|
||||
|
||||
foreach ($aggregatedColumnList as $column) {
|
||||
if (in_array($column, $reportResult->getNumericColumnList())) {
|
||||
$value = 0;
|
||||
|
||||
if (!empty($reportData->$gr)) {
|
||||
if (!empty($reportData->$gr->$column)) {
|
||||
$value = $reportData->$gr->$column;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$value = '';
|
||||
|
||||
if (property_exists($reportData, $gr) && property_exists($reportData->$gr, $column)) {
|
||||
$value = $reportData->$gr->$column;
|
||||
|
||||
if (
|
||||
!is_null($value) &&
|
||||
property_exists($reportResult->getCellValueMaps(), $column) &&
|
||||
property_exists($reportResult->getCellValueMaps()->$column, $value)
|
||||
) {
|
||||
$value = $reportResult->getCellValueMaps()->$column->$value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$row[] = $value;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
if ($depth) {
|
||||
$row = [];
|
||||
|
||||
$row[] = $this->language->translate('Total', 'labels', 'Report');
|
||||
|
||||
foreach ($aggregatedColumnList as $column) {
|
||||
if (!in_array($column, $reportResult->getNumericColumnList())) {
|
||||
$row[] = '';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sum = 0;
|
||||
|
||||
if (!empty($reportResult->getSums()->$column)) {
|
||||
$sum = $reportResult->getSums()->$column;
|
||||
}
|
||||
|
||||
$row[] = $sum;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function prepareXlsxFileName(Report $report): string
|
||||
{
|
||||
$name = preg_replace("/([^\w\s\d\-_~,;:\[\]().])/u", '_', $report->getName()) . ' ' . date('Y-m-d');
|
||||
|
||||
$fileExtension = $this->metadata->get(['app', 'export', 'formatDefs', 'xlsx', 'fileExtension']);
|
||||
|
||||
return $name . '.' . $fileExtension;
|
||||
}
|
||||
|
||||
private function prepareCsvFileName(Report $report): string
|
||||
{
|
||||
$name = preg_replace("/([^\w\s\d\-_~,;:\[\]().])/u", '_', $report->getName()) . ' ' . date('Y-m-d');
|
||||
|
||||
$fileExtension = $this->metadata->get(['app', 'export', 'formatDefs', 'csv', 'fileExtension']);
|
||||
|
||||
return $name . '.' . $fileExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkAccess(Report $report, ?User $user): void
|
||||
{
|
||||
if ($user && !$this->aclManager->checkEntityRead($user, $report)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int, mixed>>
|
||||
*/
|
||||
private function sheetDataToRaw(Xlsx\SheetData $sheetData): array
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
foreach ($sheetData->rows as $row) {
|
||||
$cells = [];
|
||||
|
||||
foreach ($row->cells as $cell) {
|
||||
$cells[] = $cell->value;
|
||||
}
|
||||
|
||||
$rows[] = $cells;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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\Tools\Report\Export\Xlsx;
|
||||
|
||||
enum CellFunction
|
||||
{
|
||||
case Sum;
|
||||
case Avg;
|
||||
case Min;
|
||||
case Max;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\Tools\Report\Export\Xlsx;
|
||||
|
||||
enum CellType
|
||||
{
|
||||
case Empty;
|
||||
case Label;
|
||||
case HeadGroup;
|
||||
case HeadNonSummary;
|
||||
case HeadSummary;
|
||||
case Summary;
|
||||
case NonSummary;
|
||||
case Total;
|
||||
case RowTotal;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\Tools\Report\Export\Xlsx;
|
||||
|
||||
readonly class DataCell
|
||||
{
|
||||
public function __construct(
|
||||
public mixed $value,
|
||||
public CellType $type,
|
||||
public ?string $fieldType = null,
|
||||
public ?CellFunction $function = null,
|
||||
public ?DateFunction $dateFunction = null,
|
||||
public ?int $decimalPlaces = null,
|
||||
public ?bool $isNumeric = null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Tools\Report\Export\Xlsx;
|
||||
|
||||
readonly class DataRow
|
||||
{
|
||||
/**
|
||||
* @param DataCell[] $cells
|
||||
*/
|
||||
public function __construct(
|
||||
public array $cells,
|
||||
public RowType $type,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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\Tools\Report\Export\Xlsx;
|
||||
|
||||
enum DateFunction
|
||||
{
|
||||
case Month;
|
||||
case Year;
|
||||
case Day;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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\Tools\Report\Export\Xlsx;
|
||||
|
||||
enum RowType
|
||||
{
|
||||
case Header;
|
||||
case DataRow;
|
||||
case HeadDataRow;
|
||||
case SubDataRow;
|
||||
case Total;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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\Tools\Report\Export\Xlsx;
|
||||
|
||||
readonly class SheetData
|
||||
{
|
||||
/**
|
||||
* @param DataRow[] $rows
|
||||
*/
|
||||
public function __construct(
|
||||
public array $rows,
|
||||
public ?int $firstSummaryRowNumber = null,
|
||||
public ?int $lastSummaryRowNumber = null,
|
||||
) {}
|
||||
}
|
||||
Reference in New Issue
Block a user