Пошагово получить информацию о заказах и сохранить как Excel (xls)

Просмотров: 2902

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

Сделано по быстрому, позже дополню заметку, завернув все проделанное в модуль с установщиком, D7 и AJAX.
Пошагово получить информацию о заказах и сохранить как Excel (xls)

Создаем директорию, например: "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>
Михаил Базаров 19.09.2022
Паузу между шагами можно выставит больше, если хостинг/сервер не справляется. Также можно увеличить или уменьшить количество заказов обрабатываемых за шаг и дополнить/удалить данные в отчете. Да, как и сказано выше: позже сделаю это модулем с Ajax и прогресс баром- пока так.
Александр Силуянов 19.09.2022
Благодарю, очень нужный функционал!