В практике разработки на платформе 1С, особенно в таких комплексных конфигурациях, как УПП (Управление производственным предприятием), часто возникает задача расчета периода просрочки или срока оплаты исключительно в рабочих днях. Для этого мы используем данные РегистрСведений.РегламентированныйПроизводственныйКалендарь. Однако при попытке наложить дополнительные условия (например, рассчитывать дни только при отсутствии оплат) программисты сталкиваются с тем, что запрос выдает некорректные итоги или «промахивается» мимо нужных строк в Системе компоновки данных (СКД).
В рамках данной статьи мы подробно разберем, как правильно соединять таблицы с производственным календарем, как обрабатывать условия по числовым полям и почему данные в консоли запросов могут отличаться от результата в отчете СКД. Для быстрой выгрузки и отладки текста запроса непосредственно из отчета компоновки данных подойдёт инженерная консоль запросов для 1С с поддержкой СКД.
Для того чтобы посчитать количество рабочих дней между двумя датами (например, датой отгрузки и текущей датой), нам необходимо выполнить левое или внутреннее соединение основной таблицы данных с регистром производственного календаря. Рассмотрим основной принцип такого соединения:
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК Календарь
ПО Календарь.ДатаКалендаря МЕЖДУ Таблица.ДатаНачала И Таблица.ДатаКонца
ГДЕ Календарь.ВидДня В (ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий),
ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный))
Проанализируем ситуацию: когда мы используем оператор МЕЖДУ в условии связи, на каждую строку исходной таблицы может наложиться несколько строк из календаря (по одной на каждый день периода). В результате количество строк в выборке увеличивается. Чтобы получить число дней, мы используем агрегатную функцию КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Календарь.ДатаКалендаря) и обязательно применяем СГРУППИРОВАТЬ ПО для всех остальных полей.
Рассмотрим случай, когда нам нужно получить количество дней только в том случае, если оплаты по документу еще не было (т.е. СуммаРасход равна нулю). Часто разработчики пытаются вставить конструкцию ВЫБОР прямо внутрь агрегатной функции КОЛИЧЕСТВО. Рассмотрим пример такого кода:
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВЫБОР
КОГДА Таблица.СуммаРегРасход = 0 И Таблица.СуммаУпрРасход = 0
ТОГДА Календарь.ДатаКалендаря
ИНАЧЕ NULL
КОНЕЦ) КАК ДнейПросрочки
Почему это может работать некорректно? Выясним причину. Если в ВЫБОР вместо NULL в блоке ИНАЧЕ указать 0, функция КОЛИЧЕСТВО посчитает этот ноль как одно уникальное значение. Таким образом, даже там, где оплаты были, отчет покажет «1». Важно помнить, что КОЛИЧЕСТВО игнорирует значения NULL, поэтому для исключения строк из подсчета следует использовать именно NULL.
Проанализируем ситуацию с «пустыми» суммами. В 1С суммы из виртуальных таблиц остатков и оборотов могут возвращать NULL, если данные за период отсутствуют. Если вы проверяете только Поле = 0, условие может не сработать для NULL. Правильнее всего использовать функцию ЕСТЬNULL на ранних этапах формирования временных таблиц:
ВЫБРАТЬ
ЕСТЬNULL(Взаиморасчеты.СуммаУпрРасход, 0) КАК СуммаУпрРасход,
...
ПОМЕСТИТЬ ВТ_Данные
ИЗ
РегистрНакопления.ВзаиморасчетыСКонтрагентами.ОстаткиИОбороты(...) КАК Взаиморасчеты
Если же вы работаете с уже сформированной временной таблицей, внутри ВЫБОР стоит добавить проверку на тип, как это было предложено в обсуждении проблемы:
ВЫБОР
КОГДА Соединение.СуммаРегРасход ЕСТЬ NULL ТОГДА 0
ИНАЧЕ Соединение.СуммаРегРасход
КОНЕЦ
Выясним, почему отчет в консоли запросов может показывать верные данные, а в СКД — нет. Основная причина кроется в механизме расчета ресурсов. Если вы определили поле ДатаКалендаря (количество дней) в запросе, но в настройках СКД на вкладке «Ресурсы» указали для него функцию Максимум или Сумма, СКД пересчитает эти значения по своим алгоритмам для каждой группировки. Для реализации нестандартных алгоритмов агрегации можно использовать методику раздельного расчета итогов для разных группировок в СКД.
Рассмотрим подробнее шаги настройки ресурсов:
КОЛИЧЕСТВО), то в СКД в качестве выражения ресурса чаще всего нужно использовать Сумма(ИмяПоля).Сумма(ВЫБОР КОГДА СуммаРасход = 0 ТОГДА ДнейПросрочки ИНАЧЕ 0 КОНЕЦ).Использование оператора ОБЪЕДИНИТЬ ВСЕ внутри вложенных запросов вместе с виртуальными таблицами ОстаткиИОбороты может сильно замедлять работу системы. Проанализируем более эффективный подход:
ВТ_ИсходныеДанные.ВТ_ИсходныеДанные с производственным календарем.Посмотрим на пример структуры оптимизированного запроса:
// Шаг 1: Подготовка данных
ВЫБРАТЬ
Данные.Контрагент,
Данные.СуммаРегРасход,
Данные.Регистратор.Дата КАК ДатаНачала,
ДОБАВИТЬКДАТЕ(Данные.Регистратор.Дата, ДЕНЬ, 10) КАК ДатаКонца
ПОМЕСТИТЬ ВТ_Подготовка
ИЗ
РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК Данные;
// Шаг 2: Подсчет дней
ВЫБРАТЬ
ВТ.Контрагент,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Календарь.ДатаКалендаря) КАК РабочихДней
ИЗ
ВТ_Подготовка КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК Календарь
ПО Календарь.ДатаКалендаря МЕЖДУ ВТ.ДатаНачала И ВТ.ДатаКонца
ГДЕ
Календарь.ВидДня В (ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий))
СГРУППИРОВАТЬ ПО
ВТ.Контрагент
Иногда при расчете просрочки пользователи жалуются, что отчет показывает «1» там, где просрочки нет. Это происходит из-за того, что границы периода в МЕЖДУ включают обе даты. Если ДатаНачала и ДатаКонца совпадают, и этот день рабочий — календарь вернет одну строку. Чтобы этого избежать, часто используют конструкцию ДОБАВИТЬКДАТЕ(ВТ.ДатаНачала, ДЕНЬ, 1), чтобы начать отсчет со следующего дня после возникновения задолженности.
Также важно следить за временем. НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) позволяет избежать ситуаций, когда документ от 12:00 дня «не видит» дату календаря, которая в базе всегда хранится как 00:00:00. В запросах автора темы этот метод применен правильно, что исключает ошибки со временем.
Подводя итог, можно сказать: для корректного решения задачи необходимо четко разделять логику выборки данных в запросе и логику агрегации в ресурсах СКД. Использование ЕСТЬNULL, правильная обработка NULL в функции КОЛИЧЕСТВО(РАЗЛИЧНЫЕ...) и вынос сложных расчетов во временные таблицы позволяют получить надежный и быстрый отчет даже в такой тяжелой конфигурации, как УПП.