В процессе разработки в среде 1С часто возникает задача автоматизации рутинных операций, например, массовой печати документов или элементов справочников по определенному условию. Типичный сценарий: в обработке необходимо выбрать все элементы справочника, у которых установлен определенный признак (флаг), и вывести для них печатную форму (макет).
В данной статье мы подробно разберем, как правильно реализовать этот механизм, проанализируем типичные ошибки начинающих разработчиков, рассмотрим вопросы оптимизации производительности, пакетной печати и интеграции с Библиотекой стандартных подсистем (БСП).
Рассмотрим исходную задачу. Разработчик хочет выбрать элементы справочника ХХ_Закупки_Materials, у которых реквизит ВыводитьНаПечать имеет значение ИСТИНА, и распечатать для них макет с именем Макет123.
В ходе обсуждения на форуме участники столкнулись с классическими ошибками проектирования кода. Посмотрим на ошибочный вариант кода, который часто пишут новички:
// ОШИБОЧНЫЙ КОД! НЕ ИСПОЛЬЗОВАТЬ В РЕАЛЬНЫХ ПРОЕКТАХ!
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХХ_Закупки_Материалы.ВыводитьНаПечать КАК ВыводитьНаПечать
|ИЗ
| Справочник.ХХ_Закупки_Материалы КАК ХХ_Закупки_Материалы";
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса = Запрос.Выполнить().Выбрать() Тогда
РезультатЗапроса.Следующий();
Объект = РезультатЗапроса.Ссылка.ПолучитьОбъект();
Объект.Печать("Макет123");
КонецЕсли;
Выясним, почему этот код не будет работать и какие архитектурные ошибки он содержит:
ВыводитьНаПечать. При этом в коде ниже разработчик пытается обратиться к свойству РезультатЗапроса.Ссылка. Платформа 1С выдаст ошибку, так как этого поля нет в выборке.Если РезультатЗапроса = Запрос.Выполнить().Выбрать() Тогда лишена смысла. Метод Выбрать() возвращает объект типа ВыборкаИзРезультатаЗапроса, который нельзя сравнивать с самим результатом запроса. Для обхода результатов необходимо использовать цикл Пока Выборка.Следующий() Цикл.ПолучитьОбъект() полностью считывает все данные элемента из базы (включая табличные части и другие реквизиты) в оперативную память. Если в справочнике тысячи элементов, это приведет к серьезному снижению производительности и перерасходу памяти СУБД и сервера приложений.Для исправления вышеописанных ошибок участники форума предложили рабочий вариант кода. Разберем его структуру подробнее:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХХ_Закупки_Материалы.ВыводитьНаПечать КАК ВыводитьНаПечать,
| ХХ_Закупки_Материалы.Ссылка КАК Ссылка
|ИЗ
| Справочник.ХХ_Закупки_Материалы КАК ХХ_Закупки_Материалы";
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
Если РезультатЗапроса.ВыводитьНаПечать Тогда
Объект = РезультатЗапроса.Ссылка.ПолучитьОбъект();
Объект.Печать("Макет123");
КонецЕсли;
КонецЦикла;
Этот код является полностью рабочим, однако его можно и нужно существенно оптимизировать.
Проанализируем ситуацию: зачем выбирать из базы данных абсолютно все элементы справочника и проверять флаг ВыводитьНаПечать на клиенте или сервере 1С, если фильтрацию можно переложить на СУБД? Перенос условий в секцию ГДЕ запроса — это базовое правило оптимизации производительности СУБД.
Кроме того, если процедура печати не требует изменения данных объекта, нам вообще не нужно использовать ПолучитьОбъект(). Мы можем передавать в процедуру печати саму ссылку.
Рассмотрим оптимизированный вариант программного кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Материалы.Ссылка КАК Ссылка
|ИЗ
| Справочник.ХХ_Закупки_Материалы КАК Материалы
|ГДЕ
| Материалы.ВыводитьНаПечать = ИСТИНА";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Вызываем процедуру печати, передавая ссылку на элемент справочника
// Сама процедура печати должна быть определена в модуле менеджера или объекта справочника
Выборка.Ссылка.Печать("Макет123");
КонецЦикла;
Вызывать процедуру печати для каждого элемента индивидуально в цикле — неэффективно. Каждый вызов генерирует отдельный табличный документ, отправляет его на принтер или выводит на экран пользователю в отдельном окне. Если у вас 50 элементов, пользователь получит 50 всплывающих окон, что сделает работу невозможной.
Лучшая практика 1С предписывает собирать все ссылки, которые подлежат выводу на печать, в единый массив, а затем передавать этот массив в процедуру печати один раз. Процедура печати вернет один общий табличный документ, где каждый элемент будет расположен на новой странице.
Разберем, как реализовать пакетный сбор данных и вывод на печать:
// Шаг 1. Собираем ссылки на элементы, которые нужно распечатать
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Материалы.Ссылка КАК Ссылка
|ИЗ
| Справочник.ХХ_Закупки_Материалы КАК Материалы
|ГДЕ
| Материалы.ВыводитьНаПечать = ИСТИНА";
СписокОбъектов = Новый Массив;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СписокОбъектов.Добавить(Выборка.Ссылка);
КонецЦикла;
// Шаг 2. Если массив не пустой, отправляем его на печать
Если СписокОбъектов.Количество() > 0 Тогда
// Вызываем процедуру формирования печатных форм
ТабличныйДокумент = Справочники.ХХ_Закупки_Материалы.СформироватьПечатнуюФорму(СписокОбъектов, "Макет123");
// Выводим полученный документ пользователю
ТабличныйДокумент.Показать("Печать материалов");
КонецЕсли;
Чтобы понять, как сформировать и вернуть табличный документ (объект типа ТабличныйДокумент), заглянем "под капот" процедуры формирования печати. Разберем по шагам классический алгоритм заполнения макета:
ТабДок = Новый ТабличныйДокумент;Макет = ПолучитьМакет("Макет123"); (или из менеджера справочника).Вывести().Посмотрим на пример реализации такой функции в модуле менеджера справочника ХХ_Закупки_Материалы:
Функция СформироватьПечатнуюФорму(МассивСсылок, ИмяМакета) Экспорт
ТабДок = Новый ТабличныйДокумент;
ТабДок.КлючПараметровПечати = "ПараметрыПечати_ХХ_Закупки_Материалы";
// Получаем макет из метаданных справочника
Макет = Справочники.ХХ_Закупки_Материалы.ПолучитьМакет(ИмяМакета);
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
// Выбираем данные для печати одним запросом по всему массиву ссылок
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Материалы.Ссылка КАК Ссылка,
| Материалы.Код КАК Код,
| Материалы.Наименование КАК Наименование
|ИЗ
| Справочник.ХХ_Закупки_Материалы КАК Материалы
|ГДЕ
| Материалы.Ссылка В (&МассивСсылок)";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
ПервыйОбъект = Истина;
Пока Выборка.Следующий() Цикл
// Если это не первый документ, вставляем разделитель страниц
Если Не ПервыйОбъект Тогда
ТабДок.ВывестиГоризонтальныйРазделительСтраниц();
КонецЕсли;
ПервыйОбъект = Ложь;
// Заполняем и выводим шапку элемента
ОбластьШапка.Параметры.КодЭлемента = Выборка.Код;
ОбластьШапка.Параметры.НаименованиеЭлемента = Выборка.Наименование;
ТабДок.Вывести(ОбластьШапка);
// Здесь также можно выводить строки табличных частей, если это необходимо
КонецЦикла;
Возврат ТабДок;
КонецФункции
В современных конфигурациях 1С (таких как ERP, КА, УТ, БП) механизмы печати стандартизированы. Рекомендуется подключать подобные обработки в качестве Внешних печатных форм (ВПФ) через подсистему "Дополнительные отчеты и обработки".
Это дает массу преимуществ: пользователям становятся доступны функции отправки сформированного документа по электронной почте, сохранения в форматы PDF/Excel, редактирования макета "на лету" без изменения конфигурации.
Для создания полноценной внешней печатной формы, совместимой с БСП, в модуле объекта внешней обработки необходимо разместить две обязательные экспортные процедуры:
// Функция регистрирует обработку в справочнике "Дополнительные отчеты и обработки"
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("3.1.2.0");
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма();
ПараметрыРегистрации.Назначение.Добавить("Справочник.ХХ_Закупки_Материалы");
ПараметрыРегистрации.Наименование = "Печать материалов (пакетная)";
ПараметрыРегистрации.Версия = "1.0";
ПараметрыРегистрации.БезопасныйРежим = Истина;
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = "Распечатать отмеченные макеты";
НоваяКоманда.Идентификатор = "Макет123";
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
НоваяКоманда.Модификатор = "ПечатьMXL";
Возврат ПараметрыРегистрации;
КонецФункции
// Процедура вызывается БСП при нажатии пользователем кнопки печати
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
ПараметрыВывода.ДоступнаПечатьПоНоминалу = Истина;
// Проверяем, нужно ли выводить наш макет
Если УправлениеПечатью.НужноПечататьМакет(КоллекцияПечатныхФорм, "Макет123") Тогда
// Формируем табличный документ
ТабличныйДокумент = СформироватьПечатнуюФорму(МассивОбъектов, "Макет123");
// Регистрируем результат в БСП
УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию(
КоллекцияПечатныхФорм,
"Макет123",
"Печатная форма материалов",
ТабличныйДокумент);
КонецЕсли;
КонецПроцедуры
Разработчики часто сталкиваются с проблемой: точки останова в модуле объекта подключенной ВПФ не срабатывают при вызове печати из интерфейса программы. Это происходит потому, что БСП загружает код обработки в безопасном режиме во временное хранилище, и отладчик теряет связь с исходным файлом .epf.
Рекомендуем использовать следующие методы для упрощения процесса отладки:
Файл -> Открыть, вы сможете легко отлаживать код построчно..epf, сымитировать передачу параметров из БСП и запустить печать с возможностью полноценной отладки в Конфигураторе./Debug), а в настройках отладки Конфигуратора включен флаг автоматического подключения фоновых заданий и внешних соединений.Применяя данные подходы, вы сможете создавать производительные, масштабируемые и удобные для конечных пользователей печатные формы, соответствующие стандартам разработки фирмы "1С".