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

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

Разработка отчетов в Системе компоновки данных (СКД) с детализацией до первичных документов движения — частая задача в среде «1С:Предприятие». Однако разработчики регулярно сталкиваются с неприятным поведением платформы: при добавлении в группировку поля Регистратор, начальные и конечные остатки в итогах отчета начинают некорректно складываться (двоиться или множиться на количество строк). В результате вместо реального остатка на конец периода мы получаем огромные, математически бессмысленные суммы.

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

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

Для начала выясним причину этой проблемы. В отличие от стандартного запроса платформы, в котором разработчик жестко прописывает предложение ИТОГИ, СКД рассчитывает итоги по остаткам динамически во временной шкале. Она делает это на основании информации о периодах, измерениях и ресурсах, указанных в настройках ролей полей набора данных.

Когда мы выбираем виртуальную таблицу остатков и оборотов с периодичностью Регистратор, СКД должна выстроить строгую хронологическую последовательность записей. Если в запросе присутствует только поле Регистратор, но отсутствует связующее поле ПериодСекунда, система теряет временной ориентир. Она не понимает, в каком порядке располагаются документы в пределах одной секунды или даже одного дня. Не имея возможности построить непрерывную временную ось, СКД отказывается от специального алгоритма расчета баланса («Остаток на начало + Оборот = Остаток на конец») и переходит к обычному математическому суммированию значений ресурсов из детальных записей. Именно поэтому конечный остаток в группировках верхнего уровня складывается из остатков по каждой строчке.

Рассмотрим детально, как решить эту проблему раз и навсегда с помощью правильной настройки ролей и структуры запроса.

Шаг 1. Добавление поля «ПериодСекунда» в запрос

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

Проанализируем пример правильного выбора полей из виртуальной таблицы регистра накопления:


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

Обратите внимание на использование параметра периодичности Авто в виртуальной таблице. Это позволяет СКД динамически получать все необходимые периоды для построения временной шкалы.

Шаг 2. Настройка ролей полей-периодов в СКД

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

Выполним следующие настройки по шагам:

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

Указание строгой иерархии периодов (номер 1 для Регистратора, номер 2 для ПериодаСекунды) сообщает СКД точную структуру подчиненности временных отметок, позволяя корректно сводить остатки в группировках любого уровня.

Шаг 3. Проверка ролей остатков (НачОст и КонОст)

Убедимся, что числовые поля остатков имеют корректные роли ресурсов:

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

Проблемы при объединении запросов (ОБЪЕДИНИТЬ ВСЕ)

Рассмотрим более сложную, но крайне распространенную ситуацию, когда разработчики объединяют данные регистра остатков с другими таблицами (например, оборотами или плановыми показателями из документов) с помощью конструкции ОБЪЕДИНИТЬ ВСЕ.

Частая ошибка в таких запросах — заполнение технических полей периодов константами вроде числа 0 или пустой строки во второй части объединения. Посмотрим на пример такой ошибочной конструкции:


ВЫБРАТЬ
    ВТ_Оплаты.ПериодСекунда КАК ПериодСекунда,
    ВТ_Оплаты.Регистратор КАК Регистратор,
    ВТ_Оплаты.СуммаКонечныйОстаток КАК СуммаОстаток
ИЗ
    ВТ_Оплаты КАК ВТ_Оплаты

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ДАТАВРЕМЯ(1, 1, 1), /* Или пустая дата */
    0,                  /* КРИТИЧЕСКАЯ ОШИБКА: число 0 вместо регистратора! */
    0
ИЗ
    ВТ_Продажи КАК ВТ_Продажи

В данном случае число 0, переданное в качестве значения для поля Регистратор, полностью ломает логику построения временной шкалы в СКД. Платформа ожидает получить в этом поле объект типа ссылка на документ либо значение НЕОПРЕДЕЛЕНО (которое СКД преобразует в NULL). Получив число, система сбивается и вновь начинает просто суммировать остатки.

Как решить эту проблему при объединении:

  1. Вместо числового нуля 0 для поля Регистратор во второй части объединения следует возвращать значение НЕОПРЕДЕЛЕНО или пустую ссылку нужного типа документа, например: ЗНАЧЕНИЕ(Документ.РеализацияТоваровУслуг.ПустаяСсылка).
  2. В настройках роли поля Регистратор на вкладке «Наборы данных» установим флаг «Игнорировать NULL». Это укажет СКД, что строки, в которых регистратор не заполнен, не должны ломать общие итоги по остаткам.
  3. Для полей периодов (например, ПериодСекунда) во второй части объединения возвращаем пустую дату: ДАТАВРЕМЯ(1, 1, 1).

Правильный вариант объединения будет выглядеть следующим образом:


ВЫБРАТЬ
    ВТ_Оплаты.ПериодСекунда КАК ПериодСекунда,
    ВТ_Оплаты.Регистратор КАК Регистратор,
    ВТ_Оплаты.СуммаКонечныйОстаток КАК СуммаОстаток
ИЗ
    ВТ_Оплаты КАК ВТ_Оплаты

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ДАТАВРЕМЯ(1, 1, 1),
    НЕОПРЕДЕЛЕНО,
    0
ИЗ
    ВТ_Продажи КАК ВТ_Продажи

Альтернативное решение: Связи наборов данных

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

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

Ошибки при жесткой фильтрации документов в «ГДЕ»

Проанализируем еще одну частую логическую ошибку разработчиков — попытку жестко ограничить состав документов-регистраторов непосредственно в условии запроса виртуальной таблицы. Например, когда нам нужно исключить из отчета документы определенного типа:


/* ТАК ДЕЛАТЬ НЕ РЕКОМЕНДУЕТСЯ! */
ГДЕ 
    НЕ ВзаиморасчетыОстаткиИОбороты.Регистратор ССЫЛКА Документ.Перевод

Если мы жестко отсекаем движения документа на уровне СУБД в секции ГДЕ, СУБД просто выбрасывает эти строки из выборки. СКД «не видит» обороты, совершенные этим документом, но при этом пытается рассчитать конечный остаток на основе начального остатка и оставшихся урезанных оборотов. В итоге расчетный конечный остаток в отчете перестанет сходиться с реальным остатком в базе данных.

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

Резюме: Чек-лист для исправления остатков в СКД

Если у вас двоятся, троятся или неверно суммируются остатки при детализации по регистратору, проверьте ваш отчет по следующему списку:

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

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

← На главную