Каждый разработчик 1С рано или поздно сталкивается с задачей, когда в отчете на базе Системы компоновки данных (СКД) требуется исключить обороты по определенным документам (например, внутренним перемещениям товаров), но при этом сохранить абсолютно верные начальные и конечные остатки. На первый взгляд задача кажется простой: достаточно наложить отбор на регистратор в виртуальной таблице или прописать условие в секции ГДЕ. Однако на практике это приводит к классической проблеме — остатки начинают «плыть».
В этой статье мы подробно разберем, почему так происходит, изучим внутренние механизмы СКД и рассмотрим три надежных способа решения этой проблемы, включая правильную настройку ролей полей компоновки данных.
Давайте подробно проанализируем поведение платформы 1С. Виртуальная таблица ОстаткиИОбороты хранит уже рассчитанные остатки на определенные моменты времени (точки), привязанные к регистраторам и периодам. Когда СКД рассчитывает начальный и конечный остатки для группировок, она выстраивает хронологическую цепочку по следующему принципу: берется остаток из первой и последней записей в выбранном периоде.
Если мы накладываем жесткий отбор в секции ГДЕ (например, исключаем документы ПеремещениеТоваров), мы физически удаляем эти строки из результирующей таблицы запроса. Цепочка периодов рвется. СКД больше не видит эти документы и в качестве первой или последней точки периода берет другие оставшиеся документы. Поскольку у оставшихся документов предрассчитанные остатки в базе данных совершенно другие (ведь перемещение на самом деле было проведено и изменило остаток на складе), СКД выводит неверные начальные и конечные остатки по группировкам. В итоге исключенная сумма оборота чудесным образом «перетекает» в начальный или конечный остаток.
Для того чтобы решить эту проблему, нам нужно исключить влияние документов на обороты, не разрушая при этом целостность временной цепочки остатков. Рассмотрим подробнее основные архитектурные способы решения этой задачи.
Этот метод является золотым стандартом разработки в 1С. Его суть заключается в том, чтобы полностью изолировать расчет остатков от фильтрации оборотов. Мы создадим два независимых набора данных-запроса и свяжем их средствами самой СКД.
Шаг 1. Создание Набора данных - запроса для получения остатков
В первом наборе данных мы будем получать только остатки. Обратите внимание: здесь не должно быть никаких фильтров по регистраторам или исключаемым документам!
ВЫБРАТЬ
ТоварыНаСкладахОстаткиИОбороты.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстаткиИОбороты.Склад КАК Склад,
ТоварыНаСкладахОстаткиИОбороты.ВНаличииНачальныйОстаток КАК ВНаличииНачальныйОстаток,
ТоварыНаСкладахОстаткиИОбороты.ВНаличииКонечныйОстаток КАК ВНаличииКонечныйОстаток,
ТоварыНаСкладахОстаткиИОбороты.ПериодСекунда КАК ПериодСекунда,
ТоварыНаСкладахОстаткиИОбороты.Регистратор КАК Регистратор
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Авто, ДвиженияИГраницыПериода, ) КАК ТоварыНаСкладахОстаткиИОбороты
Шаг 2. Создание Набора данных - запроса для получения оборотов
Во втором наборе данных мы будем получать только обороты и именно здесь наложим фильтр на исключаемые документы. Для этого оптимально использовать виртуальную таблицу Обороты.
ВЫБРАТЬ
ТоварыНаСкладахОбороты.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОбороты.Склад КАК Склад,
ТоварыНаСкладахОбороты.ВНаличииПриход КАК ВНаличииПриход,
ТоварыНаСкладахОбороты.ВНаличииРасход КАК ВНаличииРасход,
ТоварыНаСкладахОбороты.ПериодСекунда КАК ПериодСекунда,
ТоварыНаСкладахОбороты.Регистратор КАК Регистратор
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(&НачалоПериода, &КонецПериода, Авто, ) КАК ТоварыНаСкладахОбороты
ГДЕ
НЕ ТоварыНаСкладахОбороты.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
Шаг 3. Настройка связей наборов данных
Перейдем на вкладку Связи наборов данных в схеме компоновки данных и настроим связь между нашими наборами по ключевым полям:
Номенклатура = Номенклатура И Склад = СкладШаг 4. Настройка ресурсов
На вкладке Ресурсы укажем правила расчета итогов. Для полей остатков мы рассчитываем итоги по полям из первого набора данных, а для оборотов — по полям из второго. Это позволит избежать искажения данных и обеспечит корректность итоговых строк.
Если по каким-то причинам архитектура вашего отчета требует использования только одного запроса, мы можем применить другой подход. Мы не будем исключать строки физически с помощью ключевого слова ГДЕ. Вместо этого мы оставим все записи в выборке, но принудительно обнулим обороты по исключаемым документам.
Разберем пример реализации такого запроса:
ВЫБРАТЬ
ТоварыНаСкладахОстаткиИОбороты.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстаткиИОбороты.Склад КАК Склад,
ТоварыНаСкладахОстаткиИОбороты.ВНаличииНачальныйОстаток КАК ВНаличииНачальныйОстаток,
ТоварыНаСкладахОстаткиИОбороты.ВНаличииКонечныйОстаток КАК ВНаличииКонечныйОстаток,
ТоварыНаСкладахОстаткиИОбороты.ПериодСекунда КАК ПериодСекунда,
ТоварыНаСкладахОстаткиИОбороты.Регистратор КАК Регистратор,
ВЫБОР
КОГДА ТоварыНаСкладахОстаткиИОбороты.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
ТОГДА 0
ИНАЧЕ ТоварыНаСкладахОстаткиИОбороты.ВНаличииПриход
КОНЕЦ КАК ВНаличииПриход,
ВЫБОР
КОГДА ТоварыНаСкладахОстаткиИОбороты.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
ТОГДА 0
ИНАЧЕ ТоварыНаСкладахОстаткиИОбороты.ВНаличииРасход
КОНЕЦ КАК ВНаличииРасход
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Авто, ДвиженияИГраницыПериода, ) КАК ТоварыНаСкладахОстаткиИОбороты
Почему это работает: Строки с документами перемещения остаются в результирующей таблице. Благодаря этому СКД видит непрерывную хронологическую цепочку документов и правильно определяет остатки на границах периодов. При этом обороты по перемещениям будут равны нулю, поэтому они не повлияют на итоговые показатели прихода и расхода в отчете.
Очень часто остатки начинают вести себя непредсказуемо, если разработчик забыл или неправильно настроил роли полей на вкладке Наборы данных. Давайте разберем по шагам, как правильно настроить роли для полей остатков и периодов.
ВНаличииНачальныйОстаток и ВНаличииКонечныйОстаток должна быть заполнена роль Остатки. Для начального остатка выберите тип Начальный, для конечного — Конечный. Очень важно указать для них одинаковое Имя группы (например, ВНаличии). Это свяжет их в неразрывную пару.
ПериодСекунда и Регистратор необходимо присвоить роль Период. Также задайте для них порядковый номер (порядковый номер 1 для секунды, порядковый номер 2 для регистратора). Это укажет СКД правильную иерархию времени при расчете итогов.
ВЫБОР КОГДА Регистратор = НЕОПРЕДЕЛЕНО ТОГДА NULL ИНАЧЕ Регистратор КОНЕЦ. Чтобы СКД не искажала остатки по этой пустой строке, в настройках роли поля Регистратор обязательно установите флаг Игнорировать значения NULL.
В ходе обсуждения на форуме участники предлагали различные варианты решения проблемы. Рассмотрим один из них, описанный в сообщении №14:
ВЫБОР
КОГДА ТоварыНаСкладахОстаткиИОбороты.ПериодСекунда = &НачалоПериода
ТОГДА ТоварыНаСкладахОстаткиИОбороты.ВНаличииНачальныйОстаток
ИНАЧЕ 0
КОНЕЦ КАК ВНаличииНачальныйОстаток
Наш вердикт: Хотя этот метод может временно решить проблему в конкретном статичном отчете, мы настоятельно не рекомендуем использовать его на практике. Это типичный «костыль». Если пользователь решит изменить настройки группировок, добавить кросс-таблицу или изменить периодичность вывода данных в настройках СКД, этот алгоритм моментально сломается, и отчет снова выдаст некорректные остатки. Используйте только системные методы — разделение наборов данных или обнуление ресурсов без разрушения структуры выборки.