Как исправить некорректные начальные и конечные остатки в группировках отчета СКД?

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

Разберем классическую проблему, с которой часто сталкиваются разработчики при создании отчетов по остаткам и оборотам в Системе компоновки данных (СКД). Мы проектируем стандартный количественный отчет по движениям ТМЦ за период с группировкой по характеристикам, товарам и регистраторам движений. На уровне детальных записей все цифры сходятся идеально, но стоит нам свернуть группировку или добавить иерархию, как начальный остаток начинает отображать некорректные, часто завышенные или заниженные данные. Выясним причину этой проблемы и разберем по шагам, как настроить систему компоновки данных для получения абсолютно точных результатов в любых разрезах.

Почему ломаются остатки в СКД: суть проблемы

Прежде чем переходить к настройкам, проанализируем, как СКД понимает, что такое «остаток». Для СКД обычные числовые поля (например, количество прихода или расхода) — это простые ресурсы, которые можно суммировать по любой группировке с помощью стандартной функции Сумма(). Но остаток — это величина, жестко привязанная к оси времени. Его нельзя просто сложить за несколько дней. Например, если на складе 1 января было 10 штук товара, а 2 января — 12 штук, то суммарный начальный остаток за эти два дня не равен 22 штукам. Он равен 10 штукам (остаток на начало всего выбранного периода).

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

Шаг 1. Настройка парных полей остатков

Рассмотрим важнейшее правило СКД: остатки всегда должны идти парами. Для корректной работы СКД требует наличия в схеме компоновки данных обоих полей:

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

  1. Для поля начального остатка зададим роль с типом Начальный остаток.
  2. Для поля конечного остатка зададим роль с типом Конечный остаток.
  3. Важно: в параметрах роли для обоих полей впишем абсолютно одинаковое значение в поле Имя (например, Количество). Это свяжет их в единую логическую пару.

Шаг 2. Разметка периодов и иерархия временной оси

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

Добавим в наш запрос поле ПериодСекунда из виртуальной таблицы остатков и оборотов. Теперь настроим роли для полей периодов на вкладке «Наборы данных»:

Шаг 3. Роли измерений и исключение дублирования

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

Посмотрим на наши аналитические поля в наборе данных (например, Магазин, Товар, Характеристика). Для каждого из этих ключевых полей в колонке Роль обязательно установим флаг Измерение. Благодаря этому СКД поймет, что остатки нужно рассчитывать обособленно по каждому магазину, каждому товару и каждой характеристике.

Шаг 4. Правильный вывод дополнительных реквизитов (например, Код товара)

Очень часто разработчики совершают популярную ошибку: они добавляют в запрос поле ТоварКод (или Товар.Код), а затем выводят его в настройки группировки СКД наравне с самим товаром или помечают это поле как отдельное измерение в ролях.

В этом случае СКД начинает воспринимать ТоварКод как самостоятельное измерение учета. Если у двух товаров совпадут коды или если поле окажется незаполненным, остатки «поплывут». Разберем, как сделать правильно:

  1. Исключим поле ТоварКод из самостоятельных измерений (уберем флаг «Измерение» в его роли).
  2. В роли этого поля укажем свойство Родитель и выберем в качестве родителя измерение Товар. Это укажет системе, что данное поле является лишь зависимым реквизитом товара, а не самостоятельным измерением учета.
  3. Еще лучше — не добавлять ТоварКод как отдельное поле в запрос и в группировки настроек. Мы можем добавить его непосредственно в «Выбранные поля» группировки Товар через точку: Товар.Код. СКД сама оптимально подтянет его из базы без создания лишней нагрузки.

Шаг 5. Очистка регистратора в запросе

При работе с виртуальными таблицами остатков и оборотов поле Регистратор для граничных записей (остатков на начало и конец периода) часто содержит значение Неопределено. Это приводит к выводу пустых строк со значениями вида «Объект не найден».

Чтобы избежать этого, применим в тексте запроса конструкцию преобразования к NULL:


ВЫБОР
    КОГДА Регистратор = НЕОПРЕДЕЛЕНО ТОГДА NULL
    ИНАЧЕ Регистратор
КОНЕЦ КАК Регистратор

В сочетании с флагом «Игнорировать NULL» в роли периода для регистратора это позволит СКД корректно исключить эти строки из детальных записей, сохранив при этом правильный расчет остатков на границах временных отрезков.

Шаг 6. Влияние левых соединений (Left Join) в запросе

Часто для получения представлений, свойств или цен разработчики соединяют виртуальную таблицу остатков и оборотов левым соединением с другими таблицами (например, с регистром сведений цен).


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

Такое левое соединение способно полностью «сломать» автоматическое определение ролей в СКД (флаг «Автозаполнение»). Платформа теряет связь полей остатков с их исходной таблицей и перестает понимать, какие поля являются измерениями, а какие — периодами.

Если мы используем соединения в основном запросе, нам необходимо:

Анализ неоптимальных решений

Иногда разработчики, отчаявшись настроить роли в СКД, прибегают к обходным путям. Например, делают два вложенных запроса: один получает остатки на начальную дату периода, второй — на конечную дату, а затем они объединяются с оборотами. Разберем, почему этот подход крайне нежелателен:

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

Во-вторых, существенно возрастает нагрузка на базу данных. Вместо одного оптимального обращения к виртуальной таблице ОстаткиИОбороты СУБД вынуждена выполнять несколько тяжелых запросов к таблицам остатков на разные даты и производить их последующее соединение. Использование встроенного механизма ролей СКД — единственный правильный и производительный способ решения задачи.

Чек-лист для проверки отчета по остаткам и оборотам

Когда мы настраиваем отчет, выполним финальную проверку по следующему чек-листу:

  1. Присутствуют ли в схеме компоновки и начальный, и конечный остатки для каждого ресурса?
  2. Указано ли для них одинаковое имя группы в роли (например, Количество)?
  3. Заданы ли роли «Период» со сквозной нумерацией для всех полей периодов (ПериодСекунда, Регистратор и т. д.)?
  4. Стоит ли у поля Регистратор флаг «Игнорировать NULL» в настройках роли?
  5. Выделены ли все ключевые аналитики (склады, номенклатура, характеристики) как «Измерения» в ролях?
  6. Связаны ли реквизиты измерений (например, код или артикул товара) со своими родительскими измерениями через свойство «Родитель» в роли поля?
  7. Не «ломает» ли автозаполнение сложное левое соединение в запросе? Если да — настроены ли роли вручную или вынесены ли связи в настройки СКД?

Соблюдение этих простых правил гарантирует, что наш отчет будет работать максимально быстро, а цифры начальных и конечных остатков всегда будут точными при любых пользовательских группировках, отборах и настройках периодичности.

← На главную