Как избежать дублирования кода в клиентских обработчиках печати документов при использовании БСП

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

При разработке команд печати с использованием Библиотеки стандартных подсистем (БСП) программисты часто сталкиваются с ситуацией, когда логику печати необходимо реализовать на стороне клиента. Это требуется в случаях, когда нужно выгрузить данные во временный файл, запустить внешнее приложение или взаимодействовать с локальным оборудованием пользователя. Согласно стандартам БСП, мы регистрируем команду в модуле менеджера, указывая тип обработчика "Форма".

Проблема заключается в том, что такая команда автоматически появляется не только в форме самого документа, но и в форме списка. Система ожидает, что в модуле каждой из этих форм будет определена соответствующая экспортная процедура. В результате разработчик вынужден копировать один и тот же объемный код в два (или более) места, что нарушает принцип DRY (Don't Repeat Yourself) и усложняет дальнейшую поддержку. Рассмотрим, как решить эту задачу правильно и эффективно.

Решение 1. Использование общего клиентского модуля

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

Проанализируем алгоритм действий:

  1. Создадим общий модуль (например, Расш1_ПечатьДокументовКлиент) с установленными флажками Клиент (управляемое приложение).
  2. Перенесем в него всю логику обработки данных, которая ранее находилась в форме.
  3. В качестве параметров в процедуры общего модуля будем передавать контекст формы (ЭтаФорма) и массив объектов для печати.

В модуле менеджера документа при регистрации команды мы по-прежнему указываем обработчик в форме:


// В модуле менеджера
Процедура ДобавитьКомандыПечати(КомандыПечати) Экспорт
    Команда = КомандыПечати.Добавить();
    Команда.Идентификатор = "МояКомандаПечати";
    Команда.Представление = НСтр("ru = 'Печать с внешней обработкой'");
    Команда.Обработчик = "Продажи_Клиент_ПечатьВнешняя"; // Имя процедуры в формах
    Команда.СписокФорм = "ФормаОбъекта,ФормаСписка";
КонецПроцедуры

Теперь в модулях обеих форм (объекта и списка) нам достаточно написать короткий вызов:


&НаКлиенте
Процедура Продажи_Клиент_ПечатьВнешняя(ПараметрыПечати) Экспорт
    // Вызываем единый обработчик из общего модуля
    Расш1_ПечатьДокументовКлиент.ВыполнитьПечать(ЭтотОбъект, ПараметрыПечати);
КонецПроцедуры

Такой подход позволяет хранить сложный код (например, работу с ЗапуститьПриложение или асинхронными вызовами НачатьПомещениеФайла) в одном месте.

Решение 2. Программный вызов обработчика формы объекта из формы списка

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

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

Проанализируем пример реализации такой функции в общем модуле или в модуле формы списка:


&НаКлиенте
Процедура ВыполнитьКомандуПечатиДляВыделенныхСтрок(Знач ФормаСписка, Знач ПараметрыПечати) Экспорт
    Перем ИДСтроки, ИмяФормыОбъекта, КомандаФормыОбъекта, ТекущиеДанные, ФормаОбъекта;
    
    Для Каждого ИДСтроки Из ФормаСписка.Элементы.Список.ВыделенныеСтроки Цикл
        ТекущиеДанные = ФормаСписка.Элементы.Список.ДанныеСтроки(ИДСтроки);
        Если ТекущиеДанные <> Неопределено Тогда
            // Определяем полное имя формы объекта динамически
            Если ИмяФормыОбъекта = Неопределено Тогда
                // Здесь можно использовать стандартную функцию БСП для получения имени метаданных
                ИмяФормыОбъекта = ПолучитьИмяФормыОбъекта(ТекущиеДанные.Ссылка); 
            КонецЕсли;
            
            // Получаем форму объекта (без открытия пользователю)
            ПараметрыФормы = Новый Структура("Ключ", ТекущиеДанные.Ссылка);
            ФормаОбъекта = ПолучитьФорму(ИмяФормыОбъекта, ПараметрыФормы);
            
            // Находим команду в форме объекта по идентификатору из БСП
            ИмяКомандыВФорме = НайтиИмяКомандыПоИдентификатору(ФормаОбъекта, ПараметрыПечати.Идентификатор);
            КомандаФормыОбъекта = ФормаОбъекта.Команды.Найти(ИмяКомандыВФорме);
            
            Если КомандаФормыОбъекта <> Неопределено Тогда
                // Выполняем команду контекстно, как будто нажал пользователь
                ПодключаемыеКомандыКлиент.ВыполнитьКоманду(ФормаОбъекта, КомандаФормыОбъекта, ФормаОбъекта.Объект);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Важное предупреждение: Метод ПолучитьФорму в цикле является ресурсоемким. При инициализации формы происходит обращение к серверу и выполнение серверных событий (ПриСозданииНаСервере, ПриЧтенииНаСервере). Если пользователь выделит 100 строк в списке, система выполнит 100 серверных вызовов, что приведет к значительным задержкам. Данный метод стоит использовать только в исключительных случаях или при гарантированно малом количестве обрабатываемых объектов.

Решение 3. Поиск имени команды через "кишки" БСП

В современных версиях БСП (начиная с 3.1.10) структура ПараметрыПечати была изменена, и найти имя экспортной процедуры в форме стало сложнее. Чтобы программно связать команду в списке с командой в объекте, можно воспользоваться служебными данными, которые БСП хранит в параметрах формы.

Разберем, как получить внутреннее имя команды по ее идентификатору, используя временное хранилище БСП:


&НаСервереБезКонтекста
Функция ИмяВФормеПоИдентификатору(Знач АдресНастроек, Знач Идентификатор) Экспорт
    // Получаем таблицу команд из временного хранилища БСП
    Команды = ПолучитьИзВременногоХранилища(АдресНастроек);
    СтрокаТаблицы = Команды.Найти(Идентификатор, "Идентификатор");
    
    Если СтрокаТаблицы <> Неопределено Тогда
        Возврат СтрокаТаблицы.ИмяВФорме;
    КонецЕсли;
    
    Возврат "";
КонецФункции

Этот метод позволяет "прокинуть" вызов из списка в объект, зная только идентификатор команды, который остается неизменным для обеих форм.

Решение 4. Использование внешних печатных форм

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

Преимущества этого подхода:

Проанализируем структуру процедуры во внешней обработке:


&НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполнения) Экспорт
    // Здесь размещается вся клиентская логика
    // ОбъектыНазначения — это массив ссылок, выбранных в форме
    Для Каждого Ссылка Из ОбъектыНазначения Цикл
        // Обработка каждого документа
    КонецЦикла;
КонецПроцедуры

Подведем итоги

Для эффективной работы с клиентской печатью в БСП и исключения дублирования кода мы рекомендуем:

  1. Создавать общий клиентский модуль для специфической логики документов. Это самый чистый и производительный способ с точки зрения платформы 1С.
  2. Избегать массового вызова ПолучитьФорму в циклах по списку документов, чтобы не перегружать сервер.
  3. Рассматривать ВПФ как способ упаковки логики в единый модуль, если конфигурация находится на поддержке и внесение изменений в формы нежелательно.

Соблюдение этих правил сделает ваш код более читаемым, а систему — производительной и легкой в обновлении.

← На главную