При разработке отчетов в 1С (поможет инструментарий для разработки и отладки запросов и СКД) часто возникает задача отобразить динамику остатков (например, складских запасов или денежных средств) за определенный период по дням. Если использовать стандартные возможности виртуальных таблиц, мы можем столкнуться с тем, как работают виртуальные таблицы ОстаткиИОбороты и их нюансы, из-за которых возникают "пропавшие даты".
Рассмотрим эту проблему подробно. Если в определенный день не было движений (приходов или расходов), виртуальная таблица ОстаткиИОбороты с периодичностью День просто пропустит эту дату — решить проблему поможет отчет по динамике остатков и потоков денежных средств. В итоге график или таблица получаются "рваными". Разберем, как решить эту задачу и получить полные данные на каждый календарный день.
Виртуальные таблицы регистров накопления хранят данные разреженно. Это означает, что запись существует только тогда, когда было изменение состояния. Чтобы получить красивый отчет без пропусков дат, нам необходимо сформировать непрерывную ось времени.
Мы проанализируем два основных способа решения:
Это классический подход, который работает везде, даже если вы не используете СКД (например, при выгрузке данных или формировании таблицы значений). Алгоритм действий следующий:
ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN) этой таблицы дат с таблицей остатков.NULL для тех дней, когда данных не было.Посмотрим на пример того, как может быть реализована генерация последовательности дат в запросе 1С, если у нас нет готового производственного календаря. Один из элегантных способов — использование таблицы чисел (или системной таблицы) для генерации последовательности.
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, aa.a * 1000 + bb.b * 100 + cc.c * 10 + dd.d) КАК Период
ПОМЕСТИТЬ ВТ_Даты
ИЗ
(ВЫБРАТЬ 0 КАК a ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3) КАК aa
, (ВЫБРАТЬ 0 КАК b ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК bb
, (ВЫБРАТЬ 0 КАК c ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК cc
, (ВЫБРАТЬ 0 КАК d ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК dd
ГДЕ
ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, aa.a * 1000 + bb.b * 100 + cc.c * 10 + dd.d) <= &КонецПериода
;
В этом блоке мы создали временную таблицу ВТ_Даты. Теперь соединим ее с данными регистра. Важно помнить, что мы должны использовать таблицу ОстаткиИОбороты, чтобы "захватить" изменения.
Однако, простого соединения недостаточно. Если мы просто соединим даты и остатки на эти даты, мы получим остаток только на те дни, когда было движение. Чтобы получить остаток на каждый день ("протягивание" остатка), логика должна быть сложнее: нам нужно получить остаток на начало периода, а затем прибавлять к нему накопленные обороты до текущей даты календаря.
Более простой вариант в запросе — получить обороты, соединить с календарем, а затем рассчитывать нарастающий итог уже при обходе выборки или в СКД. Но если нужно именно "чистое" решение запросом, то расчет нарастающего итога и остатков по дням может выглядеть громоздко (через подзапросы). Поэтому чаще используют именно СКД.
Если вы разрабатываете отчет на Системе Компоновки Данных, задача решается значительно проще через настройку дополнения периодов в СКД без написания сложных запросов генерации дат. Разберем по шагам, как это сделать.
Шаг 1. Настройка набора данных
В запросе набора данных выбираем данные из виртуальной таблицы ОстаткиИОбороты. Обязательно выбираем поле Период с периодичностью День (или другой необходимой, например Месяц). Для этой задачи есть автоматизированный платежный календарь с остатками по дням.
ВЫБРАТЬ
ТоварыНаСкладахОстаткиИОбороты.Период КАК Период,
ТоварыНаСкладахОстаткиИОбороты.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстаткиИОбороты.КоличествоКонечныйОстаток КАК Количество
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, ДЕНЬ, , ) КАК ТоварыНаСкладахОстаткиИОбороты
Шаг 2. Настройка роли поля "Период"
Перейдем на вкладку "Наборы данных". Для поля Период в колонке "Роль" убедитесь, что указано, что это период. Обычно 1С определяет это автоматически (значок календаря с цифрой 1).
Шаг 3. Настройка дополнения данных
Перейдем на вкладку "Настройки" отчета:
Период (или сначала по Номенклатуре, а внутри — детальные записи с Периодом, в зависимости от желаемого вида отчета).Период.Период.При такой настройке СКД автоматически создаст строки для всех дат в выбранном диапазоне. Однако здесь есть важный нюанс: поля ресурсов (например, Количество) для пустых дней будут равны NULL.
Шаг 4. Обработка пустых значений (NULL)
Чтобы вместо пустоты на дату без движений показывался остаток с предыдущего дня, стандартного дополнения СКД недостаточно — оно заполняет нулями, если нет записи. Но ОстаткиИОбороты возвращают КонечныйОстаток именно на момент записи.
Для корректного вывода "протянутого" остатка в СКД рекомендуется использовать специальный синтаксис языка выражений ресурсов или виртуальную таблицу ОстаткиИОбороты с правильно настроенными параметрами:
МетодДополнения = ДвиженияИГраницыПериода. Это поможет получить начальный и конечный остатки периода.Но самый надежный способ получить остаток на каждую дату в СКД при использовании дополнения периодов — это не брать поле КонечныйОстаток напрямую, а рассчитывать его через начальный остаток + обороты, либо использовать возможности постобработки.
Вернемся к варианту с запросом. Если мы используем ВТ_Даты и соединяем её с регистром, обратим внимание на условие соединения. Если мы соединим просто по дате:
...
ИЗ
ВТ_Даты КАК Календарь
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Товары.ОстаткиИОбороты(...) КАК Данные
ПО Календарь.Период = Данные.Период
Такой подход сработает некорректно для получения остатков. Потому что в регистре запись может быть в 12:00, а календарь у нас на начало дня (00:00). Кроме того, если движений не было, соединения не произойдет.
Поэтому для задачи "остаток на каждый день" наиболее эффективным и правильным решением является:
ОстатокНачальный + Сумма(Оборот).В случае с СКД, механизм вычислит это сам, если вы правильно укажете роли полей "Начальный остаток" и "Конечный остаток" и включите дополнение периодов. СКД умеет "протягивать" остатки по периодам дополнения.
Если вам нужно просто вывести список дат в отчете — используйте Дополнение периодов в настройках группировки СКД. Это самый быстрый способ ("no-code").
Если вам нужны данные для дальнейшей программной обработки — формируйте таблицу дат через генератор чисел или используйте производственный календарь для отчетов по дням и выполняйте ЛЕВОЕ СОЕДИНЕНИЕ.