При разработке отчетов на базе Системы Компоновки Данных (СКД) разработчики часто сталкиваются с классической и очень распространенной проблемой — некорректным расчетом начальных и конечных остатков при попытке детализировать данные до документа-движения. Система начинает выдавать странные цифры: вычитает "в долг", игнорирует начальный остаток или ведет раздельный счет по измерениям, суммируя несовместимые данные. Давайте подробно разберем эту ситуацию и выясним причину такого поведения платформы 1С.
Проанализируем исходный запрос, который вызывает ошибки в расчетах:
ВЫБРАТЬ
СертификатыОстаткиИОбороты.Сертификат.Ссылка,
СертификатыОстаткиИОбороты.Клиент.Ссылка,
СертификатыОстаткиИОбороты.Период,
СертификатыОстаткиИОбороты.Регистратор,
СертификатыОстаткиИОбороты.ФормаОплаты.Ссылка,
СертификатыОстаткиИОбороты.СуммаПриход,
СертификатыОстаткиИОбороты.СуммаРасход,
СертификатыОстаткиИОбороты.СуммаНачальныйОстаток,
СертификатыОстаткиИОбороты.СуммаКонечныйОстаток
ИЗ
РегистрНакопления.Сертификаты.ОстаткиИОбороты(, , Регистратор, , ) КАК СертификатыОстаткиИОбороты
УПОРЯДОЧИТЬ ПО
СертификатыОстаткиИОбороты.Период
На первый взгляд запрос кажется логичным, но он содержит ряд фундаментальных ошибок при работе с виртуальными таблицами остатков и оборотов в связке с СКД. Рассмотрим по шагам, как правильно перестроить запрос и настройки схемы, чтобы получить математически точные итоги.
В исходном запросе параметр периодичности установлен в значение Регистратор. Это грубейшая ошибка при работе с СКД. При такой настройке система не может корректно упорядочить движения по оси времени, особенно если в одну и ту же секунду проведено несколько документов.
Сделаем правильно: периодичность в параметрах виртуальной таблицы всегда должна быть установлена в значение Авто. Кроме того, чтобы избежать проблемы "пустых строк" или некорректных нулевых остатков в периодах без движений, воспользуемся методом дополнения.
В четвертом параметре виртуальной таблицы явно укажем Движения. Это заставит систему выбирать только те периоды и регистраторы, по которым реально происходили изменения базы. Итоговый вызов виртуальной таблицы должен выглядеть так:
РегистрНакопления.Сертификаты.ОстаткиИОбороты(, , Авто, Движения, )
Чтобы СКД могла построить правильную временную шкалу и связать остатки с конкретным регистратором, нам критически необходимо поле ПериодСекунда. Даже если мы не планируем выводить его в пользовательском отчете, оно обязательно должно присутствовать в секции ВЫБРАТЬ нашего запроса. Без этого поля система физически не сможет сопоставить остатки и начнет их хаотично суммировать.
Перейдем на вкладку "Наборы данных" в СКД. Платформа рассчитывает остатки по собственным алгоритмам, опираясь исключительно на колонку "Роль". Если эти настройки сбиты, результаты будут некорректными. Настроим роли правильно:
СуммаНачальныйОстаток указываем роль НачальныйОстаток. У поля СуммаКонечныйОстаток — роль КонечныйОстаток. Важно: в свойствах обеих ролей в поле "Имя" необходимо указать одинаковое базовое имя (например, Сумма). Это скажет СКД, что эти два поля представляют собой один и тот же ресурс в разные моменты времени.Регистратор устанавливаем роль Период с номером 1. Для поля ПериодСекунда устанавливаем роль Период с номером 2.Регистратор и ПериодСекунда. Это гарантирует неявную выборку этих полей платформой, даже если пользователь удалит их из настроек структуры отчета.В обсуждаемом примере присутствует поле ФормаОплаты. В базе данных форма оплаты является измерением регистра. Это означает, что физически база хранит собственные независимые остатки по наличным, по безналичным средствам и по сертификатам.
Если мы просто выведем детальные записи, система покажет "дичь", потому что для разных форм оплаты ведется свой счетчик. Чтобы СКД могла правильно "схлопывать" остатки по этому полю (когда нет группировки по форме оплаты), полю ФормаОплаты обязательно нужно назначить роль Измерение.
Если роль указана верно, СКД поймет, что это разделитель остатков, и при получении общего итога (без разбивки) возьмет данные по всем формам за секунду и корректно их сложит.
Как было справедливо замечено в обсуждении, СКД воспринимает группировку по периоду (и регистратору) как условно конечную для корректного расчета итогов. Использовать другие поля (например, измерения) на одном уровне с периодом нельзя.
Чтобы увидеть корректную картину с разбивкой по оплате, добавим группировки иерархически: сначала по полям ПериодСекунда и Регистратор, а уже в подчинении им — группировку по полю ФормаОплаты. Это может привести к визуальному "распуханию" отчета, но итоги станут математически верными.
Рассмотрим альтернативное решение, которое часто применяется на практике, когда "распухание" отчета недопустимо. Если форма оплаты в рамках вашего бизнес-процесса является единой на весь документ (то есть это фактически реквизит шапки документа), можно отказаться от использования измерения из регистра.
Просто добавьте в запрос поле из самого регистратора:
Регистратор.ФормаОплаты КАК ФормаОплатыДокумента
В таком случае СКД будет воспринимать это поле как обычный строковый/ссылочный реквизит документа, а не как измерение регистра. Вы сможете выводить его в одной строке с регистратором, и расчет остатков не пострадает. Однако стоит помнить, что этот метод нарушает логику чтения данных из регистров и может привести к проблемам, если в будущем один документ начнет формировать движения по разным формам оплаты.
Подведем итог: корректная работа с остатками по документам-регистраторам в СКД требует жесткой дисциплины. Всегда используйте периодичность Авто, выбирайте поле ПериодСекунда, связывайте роли остатков через общее имя, правильно нумеруйте периоды по точности и не забывайте проставлять роль Измерение для всех соответствующих полей. При соблюдении этих правил ваши отчеты будут формироваться безошибочно.