Задача: Нужно получить информацию о заказах с возможностью указания даты создания "от"" и "до" и выгрузить в Excel файл. Так как заказов может быть очень много, за один шаг это не провернуть (скорее всего, сайт отвалится по таймауту) нужно сделать это пошагово с небольшой паузой между шагами.
Сделано по быстрому, позже дополню заметку, завернув все проделанное в модуль с установщиком, D7 и AJAX.

Создаем директорию, например: "order_report", что бы к ней не было доступа для всех, можно создать в директории /bitrix/components
В файле init.php добавляем запись, которая создаст пункт меню в админпанели, в разделе "Сервис":
AddEventHandler("main", "OnBuildGlobalMenu", "AddReportMenus");
function AddReportMenus(&$adminMenu, &$moduleMenu){
$moduleMenu[] = array(
"parent_menu" => "global_menu_services",
"section" => "Генератор отчетов по заказам",
"sort" => 1000,
"url" => "/bitrix/components/elements_report/step1.php",
"text" => 'Генератор отчетов по заказам',
"title" => '',
"icon" => "form_menu_icon",
"page_icon" => "form_page_icon",
"items_id" => "",
"items" => array()
);
}
В директории "elements_report" создаем три файла: generated.xls.php, step1.php, step2.php. Файл generated.xls.php оставляем пустым
Суть в том, что мы просто создаем html таблицу с отчетом, а затем с помощью передачи header-s сохраняем и скачиваем ее как xls файл. Таким же образом, можно создать эксель таблицу из HTML для любых данных.
Файл step1.php. Здесь у нас просто форма выбора даты "от" и "до" которая отправляет данные на файл step2.php
<?
require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_before.php");
$APPLICATION->SetTitle("Генератор отчета по элементам");
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_after.php");
CJSCore::Init(array("jquery"));
?>
<div class="adm-block-wrapper">
<form action="step2.php" method="post">
<input type="text" placeholder="Дата с" onclick="BX.calendar({node: this, field: this, bTime: false});"
name="dateFrom">
<input type="text" placeholder="Дата по" onclick="BX.calendar({node: this, field: this, bTime: false});"
name="dateTo">
<button type="submit" class="adm-btn adm-btn-save">Создать отчет</button>
</form>
</div>
<?
require($_SERVER["DOCUMENT_ROOT"] . BX_ROOT . "/modules/main/include/epilog_admin.php"); ?>
Файл step2.php: здесь выполняется заполнение файла generated.xls.php.
- Сначала очищается от предыдущего отчета.
- Добавляется шапка для верстки и заголовков.
- С помощью CSaleOrder::GetList получаем всю информацию о заказах отсортированных по ID и с фильтром DATE_INSERT.
- С nTopCount обрабатываем по 50 заказов за раз
- Внутри цикла заказа дополучаем информацию о корзине заказа с CSaleBasket::GetList с нужными для отчета данным.
- Формируем строку таблицы с данными о товаре и добавляем в конец файла generated.xls.php
<?php
require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_before.php");
$APPLICATION->SetTitle("Генератор отчета по заказам");
CJSCore::Init(array("jquery"));
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_after.php");
use Bitrix\Main\Loader;
use Bitrix\Sale;
Loader::includeModule("sale");
$generated_xls_php = 'generated.xls.php';
if (!empty($_POST['lastOrderId'])) {
$arFilter = array(
">=DATE_INSERT" => $_POST['dateFrom'],
"<=DATE_INSERT" => $_POST['dateTo'],
">ID" => $_POST['lastOrderId']
);
} else {
$arFilter = array(
">=DATE_INSERT" => $_POST['dateFrom'],
"<=DATE_INSERT" => $_POST['dateTo']
);
file_put_contents($generated_xls_php, '');
$fileHeader = '<?
Header("Content-Type: application/force-download");
Header("Content-Type: application/octet-stream");
Header("Content-Type: application/download");
Header("Content-Disposition: attachment;filename=excel_orders.xls");
Header("Content-Transfer-Encoding: binary");
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
td {
mso-number-format: \@;
}
.number0 {
mso-number-format: 0;
}
.number2 {
mso-number-format: Fixed;
}
</style>
</head>
<body>
<table border="1">
<tr>
<td>ID заказа</td>
<td>ID товара</td>
<td>Наименование товара</td>
<td>Наименование [ID] товара</td>
<td>Заказ: Доставка</td>
<td>Оплата: Дата оплаты</td>
<td>Оплата: Сумма</td>
<td>Отгрузка: Дата отгрузки</td>
<td>Общее количество в заказе</td>
<td>Цена товара</td>
<td>Скидка на товар</td>
<td>Количество товара</td>
<td>Сумма товара</td>
<td>Налог (%)</td>
<td>Оплата: Дата докуммента возврата</td>
<td>Статус: Наименование</td>
<td>Заказ: Заказ отменён</td>
</tr>';
file_put_contents($generated_xls_php, $fileHeader, FILE_APPEND);
}
$dbRes = CSaleOrder::GetList(
array(
'ID' => 'ASC'
),
$arFilter,
false,
array(
'nTopCount' => '50'
),
array(
'ID',
'PRICE_DELIVERY',
'DATE_PAYED',
'SUM_PAID',
'DATE_DEDUCTED',
'TAX_VALUE',
'STATUS_ID'
),
false,
);
$i = 0;
while ($order = $dbRes->Fetch()) {
$dbBasketItems = CSaleBasket::GetList(
array("NAME" => "ASC",),
array("ORDER_ID" => $order['ID']),
false,
false,
array("PRODUCT_ID", "QUANTITY", "NAME", "PRICE", "DISCOUNT_VALUE")
);
while ($arItems = $dbBasketItems->Fetch()) {
$arBasketItems[] = $arItems;
}
if ($order['CANCELED'] == 'Y') {
$canceled = 'Да';
}
$statusList = CSaleStatus::GetList(
array(),
array('ID' => $order['STATUS_ID']),
false,
false,
array('NAME')
);
while ($status = $statusList->Fetch()) {
$statusName = $status['NAME'];
}
foreach ($arBasketItems as $k => $arBasketItem) {
$orderData = '<tr>
<td>' . $order['ID'] . '</td>
<td>' . $arBasketItem['PRODUCT_ID'] . '</td>
<td>' . $arBasketItem['NAME'] . '</td>
<td>' . $arBasketItem['NAME'] . ' [' . $arBasketItem['PRODUCT_ID'] . ']</td>
<td>' . $order['PRICE_DELIVERY'] . '</td>
<td>' . $order['DATE_PAYED'] . '</td>
<td>' . $order['SUM_PAID'] . '</td>
<td>' . $order['DATE_DEDUCTED'] . '</td>
<td>' . $arBasketItem['QUANTITY'] . '</td>
<td>' . $arBasketItem['PRICE'] . '</td>
<td>' . $arBasketItem['DISCOUNT_VALUE'] . '</td>
<td>' . $arBasketItem['QUANTITY'] . '</td>
<td>' . $arBasketItem['QUANTITY'] * $arBasketItem['PRICE'] . '</td>
<td>' . $order['TAX_VALUE'] . '</td>
<td>' . $order['DATE_CANCELED'] . '</td>
<td>' . $statusName . '</td>
<td>' . $canceled . '</td>
</tr>';
file_put_contents($generated_xls_php, $orderData, FILE_APPEND);
}
$i++;
$lastOrderId = $order['ID'];
}
if ($i > 1) { ?>
<form action="" method="post" id="postStep" style="display: none;">
<input type="text" name="lastOrderId" value="<?= $lastOrderId ?>">
<input type="text" name="dateFrom" value="<?= $_POST['dateFrom'] ?>">
<input type="text" name="dateTo" value="<?= $_POST['dateTo'] ?>">
<button type="submit"></button>
</form>
<div class="waitwindow" style="width: 500px;">
Отчет готовится с шагом <?=$i?> заказов за запрос и паузой 1 секунда
</div>
<script>
function postForm() {
$("#postStep").submit();
}
setTimeout(postForm, 1000);
</script>
<?php
} else {
file_put_contents($generated_xls_php, '</table></body></html>', FILE_APPEND);?>
<a href="generated.xls.php" class="adm-btn adm-btn-save">Скачать отчет</a>
<a href="step1.php" class="adm-btn adm-btn-save">Создать новый отчет</a>
<?php
//LocalRedirect('generated.xls.php');
}?>
<?php
require($_SERVER["DOCUMENT_ROOT"] . BX_ROOT . "/modules/main/include/epilog_admin.php"); ?>
- В конце обработки пороции из 50-ти заказов, отправляем форму на этуже страницу- с изначально заданными датами и ID последнего обработанного заказа.
- Эта форма будет отправляться до тех пор, пока колличество заказов больше одного. После обработки последнего выведутся ссылки на скачивание отчета и создание нового.
При желании можете расскомментировать строку LocalRedirect('generated.xls.php');, тогда отчет скачается сразу по готовности.
Улучшение, генерируем xls сразу, без промежуточного php файла
Если файл generated.xls.php получается очень большим, сервер может не справиться с отдачей его как xls файла. На самом деле, можно сразу создать xls файл с таблицей, он точно также откроется экселем.
В файле step2.php меняем
$generated_xls_php = 'generated.xls.php';
на $generated_xls_php = 'generated.xls';
Из заголовка генерируемого файла убираем:
<?
Header("Content-Type: application/force-download");
Header("Content-Type: application/octet-stream");
Header("Content-Type: application/download");
Header("Content-Disposition: attachment;filename=excel_orders.xls");
Header("Content-Transfer-Encoding: binary");
?>
Ссылку на скачивание готового отчета меняем на:
<a href="generated.xls" download class="adm-btn adm-btn-save">
Скачать отчет
</a>