Как вывести остатки по номенклатуре на каждый день в 1С, включая дни без движений?

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

При разработке отчетов по складским остаткам программисты часто сталкиваются с тем, что стандартная виртуальная таблица ОстаткиИОбороты регистра накопления не возвращает записи за те дни, в которых не было прихода или расхода. Это происходит из-за внутренней оптимизации платформы 1С: данные выбираются только тогда, когда в регистре зафиксировано реальное событие (движение). Однако для полноценного анализа, например, для построения графиков или формирования ведомостей, нам необходимо видеть остаток на каждый календарный день периода.

В этой статье мы разберем несколько эффективных способов решения этой задачи: от использования встроенных механизмов системы компоновки данных (СКД) до сложных манипуляций в языке запросов.

Причина отсутствия данных в «пустые» дни

Прежде чем переходить к решениям, проанализируем ситуацию. Виртуальная таблица ТоварыНаСкладах.ОстаткиИОбороты при использовании периодичности День формирует выборку только на основании имеющихся записей в физической таблице регистра. Если 1-го числа товар поступил, а 5-го был продан, то при обычном запросе за период с 1-го по 5-е число вы получите только две строки. Дни со 2-го по 4-е будут отсутствовать, хотя фактически товар в эти дни лежал на складе.

Способ 1. Использование системы компоновки данных (СКД)

Это самый рекомендуемый и наименее трудозатратный способ. Система компоновки данных умеет самостоятельно «дотягивать» остатки на периоды, в которых не было оборотов. Разберем по шагам, как это настроить.

1. Настройка ролей полей

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

  1. Установим флажок «Роль» и выберем тип «Период».
  2. Укажем тип дополнения — «День».
  3. Обязательно определим границы периода (начало и конец), которые обычно привязываются к параметрам отчета &НачалоПериода и &КонецПериода.

2. Роли для полей остатков

Для полей КоличествоНачальныйОстаток и КоличествоКонечныйОстаток нужно также настроить роли. Укажем тип «Остаток», выберем имя регистра и укажем соответствующее поле периода. Это позволит СКД понимать, что если данных на конкретную дату нет, нужно взять значение из предыдущей доступной даты.

3. Настройка группировки

В настройках отчета на уровне группировки по периоду нужно установить свойство «Дополнение периода». В этом случае СКД автоматически добавит в результат недостающие строки для всех дат диапазона.

Способ 2. Использование конструкции ИТОГИ ПЕРИОДАМИ в запросе

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

Рассмотрим пример кода запроса с использованием предложения ИТОГИ:


ВЫБРАТЬ
    ТоварыНаСкладахОстаткиИОбороты.Период КАК День,
    ТоварыНаСкладахОстаткиИОбороты.Номенклатура КАК Номенклатура,
    ТоварыНаСкладахОстаткиИОбороты.КоличествоНачальныйОстаток КАК НачОстаток,
    ТоварыНаСкладахОстаткиИОбороты.КоличествоКонечныйОстаток КАК КонОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&ДатаНачала, &ДатаОкончания, День, , ) КАК ТоварыНаСкладахОстаткиИОбороты
ИТОГИ
    СУММА(НачОстаток),
    СУММА(КонОстаток)
ПО
    День ПЕРИОДАМИ(ДЕНЬ, &ДатаНачала, &ДатаОкончания),
    Номенклатура

Важный нюанс при программной обработке:

Если вы используете метод Запрос.Выполнить().Выгрузить(), вы получите обычную таблицу значений, в которой дополнения не будет. Чтобы механизм ПЕРИОДАМИ сработал, необходимо использовать обход результата через выборку. Выясним причину: дополнение периодов происходит только в момент формирования выборки с использованием специального параметра.

Проанализируем пример кода для вывода данных:


РезультатЗапроса = Запрос.Выполнить();
// Третий параметр "ВСЕ" — ключевой для дополнения периодов
ВыборкаДень = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "День", "ВСЕ");

Пока ВыборкаДень.Следующий() Цикл
    // Здесь будут даже те дни, по которым не было движений
    ВыборкаНоменклатура = ВыборкаДень.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Номенклатура");
    Пока ВыборкаНоменклатура.Следующий() Цикл
        // Обработка данных по каждой позиции номенклатуры
    КонецЦикла;
КонецЦикла;

Способ 3. Создание «сетки» дат через временную таблицу

Бывают ситуации, когда использование ИТОГИ неудобно или невозможно (например, в сложных пакетных запросах, где результат нужно передавать дальше). В таком случае мы можем принудительно создать таблицу со всеми датами периода и соединить её с данными регистра.

Разберем логику построения такого запроса:

  1. Генерация последовательности чисел: Создадим временную таблицу, содержащую числа от 0 до необходимого количества дней в периоде.
  2. Формирование дат: Используя функцию ДОБАВИТЬКДАТЕ(&ДатаНачала, ДЕНЬ, Число), превратим числа в последовательность календарных дней.
  3. Кросс-соединение с номенклатурой: Соединим таблицу дат с таблицей всей интересующей нас номенклатуры (через СОЕДИНЕНИЕ без условия или с условием ИСТИНА). Это создаст «сетку» из всех возможных пар Дата-Номенклатура.
  4. Левое соединение с остатками: К полученной сетке присоединим таблицу ОстаткиИОбороты.

Посмотрим на пример формирования таблицы дат:


ВЫБРАТЬ
    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-е число. Таким образом, остаток «протягивается» по всему периоду.

Рекомендации по выбору метода

Подведем итоги нашего исследования:

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

← На главную