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