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