Почему не выполняется цикл выборки запроса при расчете кэшбэка в 1С и как правильно реализовать начисление бонусов

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

При разработке обработок периодического расчета бонусов, кэшбэка или ретро-бонусов разработчики часто сталкиваются с ситуацией, когда процедура завершается без видимых ошибок, но начисление не происходит. При пошаговой отладке выясняется, что указатель обхода выборки даже не заходит внутрь цикла Пока Выборка.Следующий() Цикл. Разберем по шагам, почему возникает такое поведение, как исправить ошибки в тексте запроса и параметрах, а также как грамотно выстроить учет начислений в системе 1С.

Причины пропуска цикла выборки и быстрая диагностика

Если отладчик сразу переходит на строку КонецЦикла, это означает, что объект РезультатЗапроса пуст. Выборка не содержит ни одной записи, удовлетворяющей условиям секции ГДЕ.

Выясним причину с помощью первичной проверки:

  1. Проверим признак пустоты выборки сразу после выполнения запроса:
    
    РезультатЗапроса = Запрос.Выполнить();
    Сообщить(РезультатЗапроса.Пустой());
    
    Если метод возвращает Истина, значит, в информационной базе нет записей, подходящих под установленные параметры.
  2. Проверим расчет границ периода. В исходном коде для расчета за месяц была допущена ошибка: НачПериода = НачалоДня(ТекущаяДата() - 86400); Вычитание 86400 секунд (суток) из текущей даты дает интервал всего в один день, а не в месяц. Если за предыдущие сутки проведенных документов по дилерам с признаком кэшбэка не было, запрос вернет пустой результат.
  3. Проверим корректность задания булевых литералов в запросе. Передача параметров вида Запрос.УстановитьПараметр("Истина", Истина); является избыточной. В языке запросов 1С булевы литералы ИСТИНА и ЛОЖЬ поддерживаются напрямую, а для проверки булевых реквизитов достаточно написать само имя поля: ДилЗаказ.Проведен.

Корректная работа с границами периода в периодических расчетах

Рассмотрим подробнее установку интервалов дат. Использование конструкции МЕЖДУ &НачПериода И &КонПериода включает обе граничные даты. При регулярном запуске обработки (например, ежемесячно) документы, проведенные ровно в полночь на стыке периодов, могут быть посчитаны дважды.

Кроме того, если передавать в параметр дату без вызова функции КонецДня(), документы, проведенные в течение последнего дня периода, не попадут в выборку, так как время даты без явного указания интерпретируется как 00:00:00.

Разберем безопасный вариант задания периода через строгое сравнение:


// Период расчета: предыдущий месяц целиком
НачПериода = НачалоМесяца(ДобавитьМесяц(ТекущаяДата(), -1));
КонПериода = НачалоМесяца(ТекущаяДата()); // Правая граница не включается

Запрос.УстановитьПараметр("НачПериода", НачПериода);
Запрос.УстановитьПараметр("КонПериода", КонПериода);

В тексте запроса условие по дате задаем следующим образом:


ГДЕ
    ДилЗаказ.Проведен
    И ДилЗаказ.Дилер.Кэшбэк
    И ДилЗаказ.Дата >= &НачПериода
    И ДилЗаказ.Дата < &КонПериода

Оптимизация текста запроса и расчет процента

Проанализируем текст запроса. Расчет суммы бонуса эффективнее выполнять непосредственно в СУБД, а не вычислять в цикле на клиенте или сервере. При этом финансовые расчеты обязательно требуют явного округления с помощью функции ВЫРАЗИТЬ, чтобы исключить погрешности дробных чисел.

Посмотрим на исправленный и оптимизированный пример запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ДилЗаказ.Дилер КАК Дилер,
    |    СУММА(ДилЗаказ.Сумма_Итог) КАК СуммаПродаж,
    |    ВЫРАЗИТЬ(СУММА(ДилЗаказ.Сумма_Итог) * 0.06 КАК ЧИСЛО(15, 2)) КАК СуммаКэшбэка
    |ИЗ
    |    Документ.ДилЗаказ КАК ДилЗаказ
    |ГДЕ
    |    ДилЗаказ.Проведен
    |    И ДилЗаказ.Дилер.Кэшбэк
    |    И ДилЗаказ.Дата >= &НачПериода
    |    И ДилЗаказ.Дата < &КонПериода
    |СГРУППИРОВАТЬ ПО
    |    ДилЗаказ.Дилер";

Запрос.УстановитьПараметр("НачПериода", НачПериода);
Запрос.УстановитьПараметр("КонПериода", КонПериода);

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    Сообщить(СтрШаблон("Дилер: %1, Кэшбэк: %2", Выборка.Дилер, Выборка.СуммаКэшбэка));
КонецЦикла;

При отладке сложных выборок удобно выгружать результат в таблицу значений методом РезультатЗапроса.Выгрузить(), что позволяет быстро просматривать всю результирующую таблицу в отладчике.

Архитектурные проблемы расчета по документам и их решение

Прямой расчет начислений по таблицам документов несет в себе критические методологические риски. Проанализируем основные архитектурные недостатки такого подхода:

  1. Отсутствие истории настроек: реквизит Кэшбэк в справочнике Контрагенты является статическим. Если флаг включили в середине месяца, обработка задним числом рассчитает бонус по всем заказам с начала периода, что исказит учет. Решение: использование периодического регистра сведений с историей действия условий программы лояльности.
  2. Методология расчетов по документам: получение данных напрямую из документов игнорирует корректировки, возвраты и сторнирования. Корректный подход — собирать базу для начисления из движений регистров накопления (оборотных или остаточных, например, Продажи или специализированного регистра УчитываемыеПродажи).
  3. Отражение начислений: формирование документов безналичной оплаты для начисления бонуса недопустимо, так как это искажает остатки по 51 счету (банковские счета) и порождает кассовые разрывы. Начисление должно выполняться либо через специализированный документ взаимозачета/корректировки долга (относя бонус на счет 91 прочих доходов и расходов), либо через регистр накопления бонусных баллов с фиксацией документа-регистратора НачислениеБонусов.

Автоматизация выполнения через регламентные задания

Для исключения человеческого фактора начисление бонусов переносят из интерактивной внешней обработки в серверный модуль, вызываемый по расписанию через РегламентныеЗадания. Код выполняется в транзакции с предварительной установкой управляемой блокировки на расчетный период, предотвращая одновременный двойной пересчет данных несколькими сеансами.

← На главную