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