Как правильно вывести данные в параллельные колонки табличного документа в 1С без пустых строк и дублей?

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

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

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

Почему возникают дубли и пустые строки

Выясним причину дублирования данных в ячейках. В коде автора область макета получается один раз до начала цикла:


ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    // Заполняется только один параметр в зависимости от условия
    ТабДок.Вывести(ОбластьСтроки);
КонецЦикла;

Объект ОбластьСтроки сохраняет все ранее присвоенные значения параметров. Если на первой итерации был заполнен параметр СсылкаОпл, а на следующей итерации статус изменился и было присвоено значение параметру СсылкаНеОпл, то параметр СсылкаОпл останется заполненным значением из предыдущей строки. Из-за этого значение «тянется» вниз по строкам до тех пор, пока снова не будет перезаписано.

Вторая проблема — «лесенка» и пустые ячейки. Метод ТабличныйДокумент.Вывести() всегда добавляет новую строку на всю ширину отчета. Если в выборке запроса идут документы разных статусов подряд, на каждый документ создается новая строка, где заполнена лишь одна ячейка, а соседние колонки пустуют.

Дополнительно обратим внимание на важный нюанс при работе с периодами. Условие запроса СчетНаОплатуПокупателю.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания при передаче реквизита ПериодОтчета.ДатаОкончания без указания времени отсекает все документы, проведенные в течение последнего дня. Значение даты окончания необходимо передавать с границей конца дня: КонецДня(ЭтотОбъект.ПериодОтчета.ДатаОкончания).

Способ 1. Предварительное распределение по коллекциям и построчный вывод

Разберем по шагам наиболее универсальный и легко читаемый способ. Сначала распределим ссылки документов по массивам (по одному массиву на каждый статус), определим максимальное количество строк и затем выведем данные построчно в цикле.

  1. Определим массивы для каждого статуса и заполним их в процессе обхода результата запроса.
  2. Найдем максимальный размер среди всех полученных массивов.
  3. Организуем цикл по строкам от 0 до МаксимальноеКоличество - 1.
  4. На каждой итерации очищаем параметры области макета методом ОбластьСтроки.Параметры.Очистить() (или заново получаем область из макета), заполняем параметры только теми элементами массивов, которые существуют для текущего индекса, и выводим строку.

Посмотрим на пример программной реализации:


&НаСервере
Процедура СформироватьНаСервере()
    
    ТабДок = ЭтотОбъект.ТабДок;
    ТабДок.Очистить();
    
    Макет = РеквизитФормыВЗначение("Отчет").ПолучитьМакет("Макет");
    ОбластьШапки = Макет.ПолучитьОбласть("Шапка");
    ТабДок.Вывести(ОбластьШапки);
    
    ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    СчетНаОплатуПокупателю.Ссылка КАК Ссылка,
    |    СчетНаОплатуПокупателю.СтатусОплаты КАК СтатусОплаты
    |ИЗ
    |    Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
    |ГДЕ
    |    СчетНаОплатуПокупателю.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
    |    И СчетНаОплатуПокупателю.СтатусОплаты <> ЗНАЧЕНИЕ(Перечисление.СтатусОплатыСчетаПокупателю.ПустаяСсылка)";
    
    Запрос.УстановитьПараметр("ДатаНачала", ЭтотОбъект.ПериодОтчета.ДатаНачала);
    Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ЭтотОбъект.ПериодОтчета.ДатаОкончания));
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    МассивОпл     = Новый Массив;
    МассивЧастОпл = Новый Массив;
    МассивНеОпл   = Новый Массив;
    МассивОтм     = Новый Массив;
    
    Пока Выборка.Следующий() Цикл
        Если Выборка.СтатусОплаты = Перечисления.СтатусОплатыСчетаПокупателю.Оплачен Тогда
            МассивОпл.Добавить(Выборка.Ссылка);
        ИначеЕсли Выборка.СтатусОплаты = Перечисления.СтатусОплатыСчетаПокупателю.ОплаченЧастично Тогда
            МассивЧастОпл.Добавить(Выборка.Ссылка);
        ИначеЕсли Выборка.СтатусОплаты = Перечисления.СтатусОплатыСчетаПокупателю.НеОплачен Тогда
            МассивНеОпл.Добавить(Выборка.Ссылка);
        ИначеЕсли Выборка.СтатусОплаты = Перечисления.СтатусОплатыСчетаПокупателю.Отменен Тогда
            МассивОтм.Добавить(Выборка.Ссылка);
        КонецЕсли;
    КонецЦикла;
    
    МаксКоличество = Макс(
        МассивОпл.Количество(),
        МассивЧастОпл.Количество(),
        МассивНеОпл.Количество(),
        МассивОтм.Количество()
    );
    
    Для Индекс = 0 По МаксКоличество - 1 Цикл
        
        // Очищаем параметры перед заполнением новой строки
        ОбластьСтроки.Параметры.Очистить();
        
        Если Индекс < МассивОпл.Количество() Тогда
            ОбластьСтроки.Параметры.СсылкаОпл = МассивОпл[Индекс];
        КонецЕсли;
        
        Если Индекс < МассивЧастОпл.Количество() Тогда
            ОбластьСтроки.Параметры.СсылкаЧастОпл = МассивЧастОпл[Индекс];
        КонецЕсли;
        
        Если Индекс < МассивНеОпл.Количество() Тогда
            ОбластьСтроки.Параметры.СсылкаНеОпл = МассивНеОпл[Индекс];
        КонецЕсли;
        
        Если Индекс < МассивОтм.Количество() Тогда
            ОбластьСтроки.Параметры.СсылкаОтм = МассивОтм[Индекс];
        КонецЕсли;
        
        ТабДок.Вывести(ОбластьСтроки);
        
    КонецЦикла;
    
КонецПроцедуры

Способ 2. Адресация ячеек табличного документа по координатам

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

Алгоритм действий следующий:

  1. Выводим область шапки в результирующий документ.
  2. Определяем строку, с которой начинаются данные (например, если шапка занимает две строки, то данные начинаются с третьей строки).
  3. Заводим отдельный счетчик строк для каждой колонки отчета.
  4. При обходе выборки определяем колонку в зависимости от статуса, обращаемся к конкретной ячейке табличного документа, заполняем ее значение и увеличиваем счетчик строки для этой колонки.

Посмотрим на пример кода с прямой адресацией ячеек:


&НаСервере
Процедура СформироватьНаСервере()
    
    ТабДок = ЭтотОбъект.ТабДок;
    ТабДок.Очистить();
    
    Макет = РеквизитФормыВЗначение("Отчет").ПолучитьМакет("Макет");
    ОбластьШапки = Макет.ПолучитьОбласть("Шапка");
    ТабДок.Вывести(ОбластьШапки);
    
    НачальнаяСтрока = ТабДок.ВысотаТаблицы + 1;
    
    СтрокаОпл     = НачальнаяСтрока;
    СтрокаЧастОпл = НачальнаяСтрока;
    СтрокаНеОпл   = НачальнаяСтрока;
    СтрокаОтм     = НачальнаяСтрока;
    
    // Номера колонок в макете
    КолонкаОпл     = 1;
    КолонкаЧастОпл = 2;
    КолонкаНеОпл   = 3;
    КолонкаОтм     = 4;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    СчетНаОплатуПокупателю.Ссылка КАК Ссылка,
    |    СчетНаОплатуПокупателю.СтатусОплаты КАК СтатусОплаты
    |ИЗ
    |    Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
    |ГДЕ
    |    СчетНаОплатуПокупателю.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
    |    И СчетНаОплатуПокупателю.СтатусОплаты <> ЗНАЧЕНИЕ(Перечисление.СтатусОплатыСчетаПокупателю.ПустаяСсылка)";
    
    Запрос.УстановитьПараметр("ДатаНачала", ЭтотОбъект.ПериодОтчета.ДатаНачала);
    Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ЭтотОбъект.ПериодОтчета.ДатаОкончания));
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        
        Статус = Выборка.СтатусОплаты;
        
        Если Статус = Перечисления.СтатусОплатыСчетаПокупателю.Оплачен Тогда
            Ячейка = ТабДок.Область(СтрокаОпл, КолонкаОпл);
            СтрокаОпл = СтрокаОпл + 1;
        ИначеЕсли Статус = Перечисления.СтатусОплатыСчетаПокупателю.ОплаченЧастично Тогда
            Ячейка = ТабДок.Область(СтрокаЧастОпл, КолонкаЧастОпл);
            СтрокаЧастОпл = СтрокаЧастОпл + 1;
        ИначеЕсли Статус = Перечисления.СтатусОплатыСчетаПокупателю.НеОплачен Тогда
            Ячейка = ТабДок.Область(СтрокаНеОпл, КолонкаНеОпл);
            СтрокаНеОпл = СтрокаНеОпл + 1;
        ИначеЕсли Статус = Перечисления.СтатусОплатыСчетаПокупателю.Отменен Тогда
            Ячейка = ТабДок.Область(СтрокаОтм, КолонкаОтм);
            СтрокаОтм = СтрокаОтм + 1;
        Иначе
            Продолжить;
        КонецЕсли;
        
        Ячейка.Текст = Строка(Выборка.Ссылка);
        Ячейка.Расшифровка = Выборка.Ссылка;
        
    КонецЦикла;
    
КонецПроцедуры

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

← На главную