Initial commit
This commit is contained in:
634
custom/Espo/Modules/Advanced/Tools/Report/GridExportService.php
Normal file
634
custom/Espo/Modules/Advanced/Tools/Report/GridExportService.php
Normal file
@@ -0,0 +1,634 @@
|
||||
<?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-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Tools\Report;
|
||||
|
||||
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\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\Core\Report\ExportXlsx;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
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\Crm\Entities\Opportunity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Tools\Pdf\Data;
|
||||
use Espo\Tools\Pdf\Service as PdfService;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Exception;
|
||||
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
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
*/
|
||||
public function exportXlsx(string $id, ?WhereItem $where, ?User $user = null): string
|
||||
{
|
||||
/** @var ?Report $report */
|
||||
$report = $this->entityManager->getEntityById(Report::ENTITY_TYPE, $id);
|
||||
|
||||
if (!$report) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
if ($user && !$this->aclManager->checkEntityRead($user, $report)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$contents = $this->buildXlsxContents($id, $where, $user);
|
||||
|
||||
$name = preg_replace("/([^\w\s\d\-_~,;:\[\]().])/u", '_', $report->getName()) . ' ' . date('Y-m-d');
|
||||
|
||||
$mimeType = $this->metadata->get(['app', 'export', 'formatDefs', 'xlsx', 'mimeType']);
|
||||
$fileExtension = $this->metadata->get(['app', 'export', 'formatDefs', 'xlsx', 'fileExtension']);
|
||||
|
||||
$fileName = $name . '.' . $fileExtension;
|
||||
|
||||
$attachment = $this->entityManager->getNewEntity(Attachment::ENTITY_TYPE);
|
||||
|
||||
$attachment->set('name', $fileName);
|
||||
$attachment->set('role', 'Export File');
|
||||
$attachment->set('type', $mimeType);
|
||||
$attachment->set('contents', $contents);
|
||||
$attachment->set([
|
||||
'relatedType' => Report::ENTITY_TYPE,
|
||||
'relatedId' => $id,
|
||||
]);
|
||||
|
||||
$this->entityManager->saveEntity($attachment);
|
||||
|
||||
return $attachment->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws Exception
|
||||
* @throws BadRequest
|
||||
*/
|
||||
public function buildXlsxContents(string $id, ?WhereItem $where, ?User $user = null): string
|
||||
{
|
||||
/** @var ?Report $report */
|
||||
$report = $this->entityManager->getEntityById(Report::ENTITY_TYPE, $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);
|
||||
}
|
||||
|
||||
if (!$reportResult) {
|
||||
$reportResult = $this->service->runGrid($id, $where, $user);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
if ($groupCount === 2) {
|
||||
foreach ($reportResult->getSummaryColumnList() as $column) {
|
||||
$result[] = $this->getGridReportResultForExport($id, $where, $column, $user, $reportResult);
|
||||
}
|
||||
} else {
|
||||
$result[] = $this->getGridReportResultForExport($id, $where, null, $user, $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];
|
||||
}
|
||||
}
|
||||
|
||||
$exportParams = [
|
||||
'exportName' => $report->getName(),
|
||||
'columnList' => $columnList,
|
||||
'columnTypes' => $columnTypes,
|
||||
'chartType' => $report->get('chartType'),
|
||||
'groupByList' => $groupByList,
|
||||
'columnLabels' => $columnLabels,
|
||||
'reportResult' => $reportResult,
|
||||
'groupLabel' => '',
|
||||
];
|
||||
|
||||
if ($groupCount) {
|
||||
$group = $groupByList[$groupCount - 1];
|
||||
$exportParams['groupLabel'] = $this->gridUtil->translateGroupName($entityType, $group);
|
||||
}
|
||||
|
||||
$export = $this->injectableFactory->create(ExportXlsx::class);
|
||||
|
||||
return $export->process($entityType, $exportParams, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
*/
|
||||
public function exportCsv(
|
||||
string $id,
|
||||
?WhereItem $where,
|
||||
?string $column = null,
|
||||
?User $user = null
|
||||
): string {
|
||||
|
||||
/** @var ?Report $report */
|
||||
$report = $this->entityManager->getEntityById(Report::ENTITY_TYPE, $id);
|
||||
|
||||
if (!$report) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
if ($user && !$this->aclManager->checkEntityRead($user, $report)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$contents = $this->getGridReportCsv($id, $where, $column, $user);
|
||||
|
||||
$name = preg_replace("/([^\w\s\d\-_~,;:\[\]().])/u", '_', $report->getName()) . ' ' . date('Y-m-d');
|
||||
|
||||
$mimeType = $this->metadata->get(['app', 'export', 'formatDefs', 'csv', 'mimeType']);
|
||||
$fileExtension = $this->metadata->get(['app', 'export', 'formatDefs', 'csv', 'fileExtension']);
|
||||
|
||||
$fileName = $name . '.' . $fileExtension;
|
||||
|
||||
$attachment = $this->entityManager->getEntity('Attachment');
|
||||
|
||||
$attachment->set('name', $fileName);
|
||||
$attachment->set('role', 'Export File');
|
||||
$attachment->set('type', $mimeType);
|
||||
$attachment->set('contents', $contents);
|
||||
$attachment->set([
|
||||
'relatedType' => Report::ENTITY_TYPE,
|
||||
'relatedId' => $id,
|
||||
]);
|
||||
|
||||
$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, $where, $column, $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,
|
||||
?GridResult $reportResult = null
|
||||
): array {
|
||||
|
||||
if (!$reportResult) {
|
||||
$reportResult = $this->service->runGrid($id, $where, $user);
|
||||
}
|
||||
|
||||
$depth = count($reportResult->getGroupByList());
|
||||
$reportData = $reportResult->getReportData();
|
||||
|
||||
$result = [];
|
||||
|
||||
if ($depth == 2) {
|
||||
$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;
|
||||
}
|
||||
|
||||
} else if ($depth === 1 || $depth === 0) {
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCellDisplayValueFromResult(
|
||||
int $groupIndex,
|
||||
string $gr1,
|
||||
string $column,
|
||||
GridResult $reportResult
|
||||
) {
|
||||
|
||||
$groupName = $reportResult->getGroupByList()[$groupIndex];
|
||||
|
||||
$dataMap = $reportResult->getNonSummaryData()->$groupName;
|
||||
|
||||
$value = '';
|
||||
|
||||
if ($this->gridHelper->isColumnNumeric($column, $reportResult)) {
|
||||
$value = 0;
|
||||
}
|
||||
|
||||
if (
|
||||
property_exists($dataMap, $gr1) &&
|
||||
property_exists($dataMap->$gr1, $column)
|
||||
) {
|
||||
$value = $dataMap->$gr1->$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function exportPdf(
|
||||
string $id,
|
||||
?WhereItem $where,
|
||||
string $templateId,
|
||||
?User $user = null
|
||||
): string {
|
||||
|
||||
$report = $this->entityManager->getEntityById(Report::ENTITY_TYPE, $id);
|
||||
$template = $this->entityManager->getEntityById(Template::ENTITY_TYPE, $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,
|
||||
];
|
||||
|
||||
$pdfService = $this->injectableFactory->create(PdfService::class);
|
||||
|
||||
$contents = $pdfService
|
||||
->generate(
|
||||
Report::ENTITY_TYPE,
|
||||
$report->getId(),
|
||||
$template->getId(),
|
||||
null,
|
||||
Data::create()->withAdditionalTemplateData((object) $additionalData)
|
||||
)
|
||||
->getString();
|
||||
|
||||
$attachment = $this->entityManager->createEntity(Attachment::ENTITY_TYPE, [
|
||||
'contents' => $contents,
|
||||
'role' => 'Export File',
|
||||
'type' => 'application/pdf',
|
||||
'relatedId' => $id,
|
||||
'relatedType' => Report::ENTITY_TYPE,
|
||||
]);
|
||||
|
||||
return $attachment->getId();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user