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