Перейти к содержимому

Отчёты по заказам - агрегаты, группировка, выгрузка

Считаем выручку и заказы так, чтобы отчёт открывался за секунду, а не тянул десятки тысяч записей в память ради одной суммы.

Решение

Считаем агрегаты запросом к базе:

use Bitrix\Sale\Internals\OrderTable;
$row = OrderTable::getList([
'select' => [
'CNT' => new \Bitrix\Main\Entity\ExpressionField('CNT', 'COUNT(%s)', 'ID'),
'SUMMA' => new \Bitrix\Main\Entity\ExpressionField('SUMMA', 'SUM(%s)', 'PRICE'),
],
'filter' => ['=PAYED' => 'Y', '!=STATUS_ID' => 'CANCELED'],
])->fetch();

Суммы и количества считает база, а не код на странице. Выборка десяти тысяч заказов ради одной суммы съедает память и время, а тот же результат приходит одной строкой за доли секунды.

Группируем по дням и статусам:

$rows = OrderTable::getList([
'select' => ['STATUS_ID',
'DAY' => new \Bitrix\Main\Entity\ExpressionField('DAY', 'DATE(%s)', 'DATE_INSERT'),
'SUMMA' => new \Bitrix\Main\Entity\ExpressionField('SUMMA', 'SUM(%s)', 'PRICE')],
'filter' => ['>=DATE_INSERT' => $from],
'group' => ['STATUS_ID', 'DAY'],
])->fetchAll();

Группировка строк задаётся в самом запросе к базе. Разбивка по дням и статусам приходит уже готовой, и коду остаётся только разложить строки в таблицу для показа.

Убираем из выручки лишнее:

$filter = [
'=PAYED' => 'Y', // считаем оплаченные
'!=CANCELED' => 'Y', // без отменённых
'!=STATUS_ID' => 'TEST', // и без тестовых, если такой статус есть
];
// договорённость о том, что считается выручкой, важнее самого запроса

Договорённость о том, что считается выручкой, важнее кода отчёта. Оплаченные, отгруженные и просто оформленные заказы дают три разных числа, и спор о них случается на первой же встрече по отчётам.

Считаем тяжёлый отчёт по расписанию:

// ночью считаем и кладём в свою таблицу, днём показываем готовое
SalesDailyTable::add(['DAY' => $day, 'CNT' => $row['CNT'], 'SUMMA' => $row['SUMMA']]);
// отчёт за год по клику в час пик - это минуты нагрузки на базу

Тяжёлый отчёт считают заранее и показывают уже готовым. Отчёт за год, запускаемый менеджером в час пик, соревнуется за базу с покупателями и обычно выигрывает.

Отчёт стоит начинать с вопроса «какое решение он поменяет». Красивая таблица, на которую никто не смотрит дважды, стоит столько же времени, сколько и полезная, а пользы не приносит вовсе.

Часовой пояс сдвигает границы суток. Отчёт «за вчера», посчитанный по времени сервера, у магазина с покупателями из других часовых поясов расходится с их представлением о вчера на несколько заказов.

Числа отчёта стоит сверять с учётной системой хотя бы раз в месяц. Расхождение, найденное в конце квартала, обходится дороже, чем найденное в тот же день.

Типичные проблемы

Отчёт съедает всю память процесса.

Заказы выбираются в память, а суммы считаются кодом. Агрегаты считает база одним запросом.

Выручка не сходится с учётной системой.

В сумму попали отменённые или неоплаченные заказы. Состав выручки задают фильтром и договорённостью.

Отчёт роняет сайт в час пик.

Тяжёлый запрос запускается по клику менеджера. Такие отчёты считают ночью и хранят готовыми.

Числа за вчера отличаются у менеджеров.

Границы суток считаются в разных часовых поясах. Пояс отчёта фиксируют явно.

Отчёт по свойству заказа работает минуту.

Свойства лежат в отдельной таблице, и отбор по ним требует соединения. Часто используемым полям добавляют указатель.

Частые вопросы

Где хранить посчитанные отчёты?

В своей таблице с разбивкой по дням. Тогда любой период собирается сложением готовых строк.

Как считать средний чек?

Отношением суммы к числу заказов в том же запросе. Считать его по каждому заказу отдельно не нужно.

Нужен ли отдельный интерфейс отчётов?

Если к отчёту возвращаются - да, страницей в административной части. Разовый вопрос закрывает выгрузка в файл.

Что делать с возвратами?

Вычитать их из выручки того периода, когда они случились. Правка задним числом ломает уже сданные отчёты.

Смежное

Первоисточник