Как убрать пустую строку регистратора в Универсальном отчете 1С?

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

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

Выясняем причину: почему появляется пустая строка?

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

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

При выводе детальных записей в Универсальном отчете эта служебная строка выводится наряду с обычными документами движения, образуя ту самую раздражающую пустую строку в табличном документе.

Способ 1. Настройка итогов в запросе и отключение детальных записей

Это классическое и наиболее правильное решение в рамках Универсального отчета. Очень часто разработчики совершают ошибку: они оставляют вывод детальных записей включенным, но не настраивают итоги в самом запросе. В результате при отключении детальных записей отчет просто перестает показывать числовые показатели на уровнях группировок.

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

УниверсальныйОтчет.ВыводитьДетальныеЗаписи = Ложь;

Во-вторых, мы должны переписать секцию итогов в запросе так, чтобы агрегатные функции (такие как СУММА) рассчитывались по всем необходимым группировкам. Рассмотрим пример правильной структуры запроса:


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

При такой настройке Универсальный отчет сгруппирует данные по измерениям строк, а расчет показателей выполнится на уровне группировок. Поскольку детальные записи отключены, виртуальная пустая строка с НЕОПРЕДЕЛЕНО просто не попадет в итоговый табличный документ.

Способ 2. Изменение метода дополнения периода в запросе

Если нам не требуется видеть остатки по тем позициям номенклатуры, по которым вообще не было никаких движений за выбранный период, мы можем применить очень простое решение — изменить параметры виртуальной таблицы.

По умолчанию виртуальная таблица ОстаткиИОбороты использует метод дополнения ДвиженияИГраницыПериода. Именно этот режим заставляет систему принудительно генерировать пустые строки для фиксации остатков на границах. Если мы заменим его на метод Движения (четвертый параметр виртуальной таблицы), лишние строки исчезнут. Проанализируем пример изменения параметров таблицы в запросе:


ИЗ
    РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&ДатаНач, &ДатаКон, Регистратор, Движения, ) КАК ТоварыНаСкладахОстаткиИОбороты

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

Способ 3. Использование жесткого отбора в секции ГДЕ

Еще один быстрый, но требующий крайней осторожности способ — это явное отсечение пустых регистраторов на уровне фильтрации запроса. Мы можем добавить условие в секцию ГДЕ нашего запроса:


ГДЕ
    ТоварыНаСкладахОстаткиИОбороты.Регистратор <> НЕОПРЕДЕЛЕНО
    И ТоварыНаСкладахОстаткиИОбороты.Регистратор ЕСТЬ НЕ NULL

Разберем последствия применения такого фильтра. Данный подход гарантированно уберет пустую строку из отчета. Однако, как и в предыдущем способе, это нарушит корректность расчета начальных и конечных остатков для тех позиций, которые не имели движений в выбранном интервале дат. Используйте этот метод только в тех отчетах, где анализируются исключительно обороты (приходы и расходы), без вывода начальных и конечных остатков.

Способ 4. Разделение запроса на Обороты и Остатки (Соединение таблиц)

Если вам необходимы абсолютно точные начальные и конечные остатки по всем складам и номенклатурным позициям (даже без движений), но при этом вы хотите видеть детальные записи по регистраторам без пустых строк, стандартная таблица ОстаткиИОбороты в один проход не поможет. В этом случае мы рекомендуем использовать разделение запроса.

Схема реализации выглядит следующим образом:

  1. Создаем первую временную таблицу, в которой выбираем только Остатки (начальные и конечные) из таблицы Остатки (или ОстаткиИОбороты без периодичности по регистратору). В ней группировка идет только по Складу и Номенклатуре.
  2. Создаем вторую временную таблицу, куда выбираем только Обороты (приходы и расходы) из таблицы Обороты с периодичностью Регистратор. Здесь у нас гарантированно не будет никаких пустых регистраторов, так как обороты всегда привязаны к конкретным документам.
  3. В финальном запросе объединяем эти таблицы с помощью ЛЕВОГО СОЕДИНЕНИЯ или конструкции ОБЪЕДИНИТЬ ВСЕ с последующей группировкой.

Посмотрим на пример реализации такого сложного, но максимально надежного запроса:


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

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

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

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВсеДанные.Склад КАК Склад,
    ВсеДанные.Номенклатура КАК Номенклатура,
    ВсеДанные.Регистратор КАК Регистратор,
    СУММА(ВсеДанные.КоличествоНачальныйОстаток) КАК КоличествоНачальныйОстаток,
    СУММА(ВсеДанные.КоличествоКонечныйОстаток) КАК КоличествоКонечныйОстаток,
    СУММА(ВсеДанные.КоличествоПриход) КАК КоличествоПриход,
    СУММА(ВсеДанные.КоличествоРасход) КАК КоличествоРасход
ИЗ
    (ВЫБРАТЬ
        ВТ_Остатки.Склад КАК Склад,
        ВТ_Остатки.Номенклатура КАК Номенклатура,
        ВТ_Остатки.Регистратор КАК Регистратор,
        ВТ_Остатки.КоличествоНачальныйОстаток КАК КоличествоНачальныйОстаток,
        ВТ_Остатки.КоличествоКонечныйОстаток КАК КоличествоКонечныйОстаток,
        ВТ_Остатки.КоличествоПриход КАК КоличествоПриход,
        ВТ_Остатки.КоличествоРасход КАК КоличествоРасход
    ИЗ
        ВТ_Остатки КАК ВТ_Остатки
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
        ВТ_Обороты.Склад,
        ВТ_Обороты.Номенклатура,
        ВТ_Обороты.Регистратор,
        ВТ_Обороты.КоличествоНачальныйОстаток,
        ВТ_Обороты.КоличествоКонечныйОстаток,
        ВТ_Обороты.КоличествоПриход,
        ВТ_Обороты.КоличествоРасход
    ИЗ
        ВТ_Обороты КАК ВТ_Обороты) КАК ВсеДанные

СГРУППИРОВАТЬ ПО
    ВсеДанные.Склад,
    ВсеДанные.Номенклатура,
    ВсеДанные.Регистратор

Этот подход полностью решает проблему "лишней строки" на уровне самой СУБД, гарантируя при этом стопроцентную математическую точность всех итогов и остатков по складу и номенклатуре.

Способ 5. Программное удаление строки в результирующем табличном документе

Если у вас сложный существующий отчет и вы не хотите переписывать запросы или менять настройки Построителя отчета, можно применить "косметическое" решение. Мы можем программно обойти готовый табличный документ перед выводом его на экран пользователю и удалить ненужные пустые строки.

Для этого в модуле отчета, в процедуре вывода, добавим следующий алгоритм поиска и удаления строк:


ТабДок = Новый ТабличныйДокумент;
УниверсальныйОтчет.ВывестиРезультирующийДокумент(ТабДок);

// Начинаем обход строк снизу вверх, чтобы не сбивались индексы при удалении
Для НомерСтроки = -ТабДок.ВысотаТаблицы По 1 Цикл
    ТекущийНомер = -НомерСтроки;
    ОбластьСтроки = ТабДок.Область(ТекущийНомер, 1, ТекущийНомер, ТабДок.ШиринаТаблицы);
    
    // Проверяем ячейку, где должен выводиться Регистратор
    // Обычно это определенная колонка, например, колонка 3
    ЗначениеЯчейки = ТабДок.Область(ТекущийНомер, 3).Текст;
    
    Если ЗначениеЯчейки = "" Или ЗначениеЯчейки = "<...>" Тогда
        ТабДок.УдалитьОбласть(ОбластьСтроки, ТипСмещенияТабличногоДокумента.ПоВертикали);
    КонецЕсли;
КонецЦикла;

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

Альтернатива в Системе компоновки данных (СКД)

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

Мы подробно разобрали пять различных способов борьбы с пустой строкой регистратора в Универсальном отчете. Выбирайте наиболее подходящий вариант в зависимости от ваших требований к производительности и точности вывода остатков!

← На главную