Разработка внешних печатных форм в среде 1С:Предприятие 8.3 с использованием Библиотеки Стандартных Подсистем (БСП) — задача стандартная, но часто преподносящая неприятные сюрпризы. Программисты регулярно сталкиваются с двумя критическими проблемами при попытке сформировать печатную форму в формате MS Word:
В этой статье мы подробно проанализируем причины возникновения этих ошибок и разберем по шагам два надежных варианта реализации печати документов Word: классический клиентский метод через COM-соединение и современный кроссплатформенный метод БСП без использования COM-объектов на сервере.
Когда мы регистрируем внешнюю печатную форму в справочнике «Дополнительные отчеты и обработки», БСП считывает параметры из экспортной функции СведенияОВнешнейОбработке. Если в параметрах указан вариант запуска ВызовКлиентскогоМетода, система ожидает, что в обработке присутствует основная управляемая форма, а в модуле этой формы объявлена экспортная процедура Печать с строго определенной сигнатурой.
Выясним причину, почему платформа выдает ошибку, даже если код процедуры написан верно:
ВызовСерверногоМетода, а затем изменили код на ВызовКлиентскогоМетода и просто обновили файл через кнопку «Обновить из файла», изменения могут не примениться. Система продолжит искать метод в модуле объекта, а не в модуле формы. Решение: полностью удалить элемент из справочника дополнительных обработок, закрыть сеанс 1С, запустить его заново и зарегистрировать файл «с нуля».
&НаКлиенте
Процедура Печать(ИдентификаторКоманды, МассивОбъектов) Экспорт
// Код вызова формирования макета
КонецПроцедуры
Рассмотрим подробнее ситуацию, когда клиентский метод успешно вызывается, но сеанс намертво зависает, выводя сообщение о выполнении команды. Это происходит из-за грубого нарушения клиент-серверной архитектуры при работе с COM-объектами.
Проанализируем типичный сценарий ошибки. В модуле формы разработчик вызывает процедуру СобратьМакет(Элемент). Если эта процедура выполняется на сервере (или вызывает серверный контекст), внутри нее происходит следующая инициализация:
// Ошибка! Создание COM-объекта Word на сервере
MSWord = Новый COMОбъект("Word.Application");
В клиент-серверной версии 1С сервер приложений физически выполняется на удаленном компьютере под управлением службы (обычно под учетной записью usr1cv8). Когда серверный код вызывает конструктор COMОбъект("Word.Application"), происходит следующее:
WINWORD.EXE в так называемой «Сессии 0» (Session 0) — изолированной среде для системных служб.Золотое правило разработки: создавать COM-объект Word.Application можно строго на клиенте (&НаКлиенте). На сервере допускается лишь подготовка чистых данных (структур, таблиц значений, массивов), которые затем передаются на клиентскую сторону для заполнения шаблона.
Если вам требуется реализовать интерактивное заполнение шаблона Word на рабочем компьютере пользователя, где установлен Microsoft Office, разберем по шагам корректный алгоритм.
Шаг 1. Пропишем правильную регистрацию в модуле объекта внешней обработки. Обратите внимание, что мы явно указываем вариант запуска ВызовКлиентскогоМетода:
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = Новый Структура;
МассивНазначений = Новый Массив;
МассивНазначений.Добавить("Справочник.Сотрудники");
ПараметрыРегистрации.Вставить("Вид", "ПечатнаяФорма");
ПараметрыРегистрации.Вставить("Назначение", МассивНазначений);
ПараметрыРегистрации.Вставить("Наименование", "Трудовой договор с сотрудником (WORD)");
ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь); // Для работы с COM-объектами на клиенте
ПараметрыРегистрации.Вставить("Версия", "1.0");
ПараметрыРегистрации.Вставить("Информация", "Печать трудового договора в формате Word через COM");
ТаблицаКоманд = ПолучитьТаблицуКоманд();
ДобавитьКоманду(ТаблицаКоманд,
"Трудовой договор (WORD)",
"ТрудовойДоговор",
"ВызовКлиентскогоМетода", // Вызываем форму обработки
Истина,
""); // Модификатор оставляем пустым
ПараметрыРегистрации.Вставить("Команды", ТаблицаКоманд);
Возврат ПараметрыРегистрации;
КонецФункции
Функция ПолучитьТаблицуКоманд()
Команды = Новый ТаблицаЗначений;
Команды.Колонки.Добавить("Представление", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("Идентификатор", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("Использование", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("ПоказыватьОповещение", Новый ОписаниеТипов("Булево"));
Команды.Колонки.Добавить("Модификатор", Новый ОписаниеТипов("Строка"));
Возврат Команды;
КонецФункции
Процедура ДобавитьКомунду(ТаблицаКоманд, Представление, Идентификатор, Использование, ПоказыватьОповещение, Модификатор)
НоваяКоманда = ТаблицаКоманд.Добавить();
НоваяКоманда.Представление = Представление;
НоваяКоманда.Идентификатор = Идентификатор;
НоваяКоманда.Использование = Использование;
НоваяКоманда.ПоказыватьОповещение = ПоказыватьОповещение;
НоваяКоманда.Модификатор = Модификатор;
КонецПроцедуры
Шаг 2. Разработаем модуль основной управляемой формы нашей обработки. Здесь мы разделим логику: на сервере получим данные для заполнения договора и двоичные данные макета, а на клиенте — создадим процесс Word, сохраним макет во временный файл и произведем автозамену параметров.
&НаКлиенте
Процедура Печать(ИдентификаторКоманды, МассивОбъектов) Экспорт
Для Каждого ТекущийОбъект Из МассивОбъектов Цикл
// Получаем структуру данных сотрудника с сервера
ДанныеДляЗаполнения = ПолучитьДанныеСотрудникаНаСервере(ТекущийОбъект);
// Получаем двоичные данные шаблона Word с сервера
ДвоичныеДанныеМакета = ПолучитьМакетНаСервере();
Если ДвоичныеДанныеМакета = Неопределено Тогда
ВызватьИсключение "Не удалось получить макет договора из двоичных данных!";
КонецЕсли;
// Сохраняем макет во временный файл на диске клиента
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("docx");
ДвоичныеДанныеМакета.Записать(ИмяВременногоФайла);
Попытка
// Инициализируем COM-объект строго на клиенте
WordПриложение = Новый COMОбъект("Word.Application");
Исключение
Сообщить("Не удалось запустить MS Word. Проверьте, установлена ли программа на вашем ПК!", СтатусСообщения.Важное);
Возврат;
КонецПопытки;
Попытка
// Открываем сохраненный временный файл
Документ = WordПриложение.Documents.Open(ИмяВременногоФайла);
WordПриложение.Visible = Ложь; // Скрываем окно на время автозамены
// Заменяем теги в тексте документа
ЗаменитьПараметрВДокументе(WordПриложение, "<КраткоеНаименованиеОрганизации>", ДанныеДляЗаполнения.Организация);
ЗаменитьПараметрВДокументе(WordПриложение, "<ДолжностьРуководителя>", ДанныеДляЗаполнения.ДолжностьРуководителя);
ЗаменитьПараметрВДокументе(WordПриложение, "<Руководитель>", ДанныеДляЗаполнения.ФИОРуководителя);
ЗаменитьПараметрВДокументе(WordПриложение, "<Сотрудник>", ДанныеДляЗаполнения.ФИОСотрудника);
// Делаем окно Word видимым пользователю
WordПриложение.Visible = Истина;
WordПриложение.Activate();
Исключение
// В случае ошибки обязательно закрываем фоновые процессы Word
Если ЗначениеЗаполнено(Документ) Тогда
Документ.Close(Ложь);
КонецЕсли;
WordПриложение.Quit();
Сообщить("Произошла ошибка при формировании документа: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ЗаменитьПараметрВДокументе(WordПриложение, ИмяПараметра, ЗначениеПараметра)
Поиск = WordПриложение.Selection.Find;
Поиск.ClearFormatting();
Поиск.Text = ИмяПараметра;
Поиск.Replacement.ClearFormatting();
Поиск.Replacement.Text = Строка(ЗначениеПараметра);
// Параметры метода Execute: FindText, MatchCase, MatchWholeWord, MatchWildcards,
// MatchSoundsLike, MatchAllWordForms, Forward, Wrap, Format, ReplaceWith, Replace
Поиск.Execute(,,,,,,,,,, 2); // 2 означает заменить все вхождения
КонецПроцедуры
&НаСервере
Функция ПолучитьДанныеСотрудникаНаСервере(СсылкаСотрудник)
Данные = Новый Структура;
Данные.Вставить("Организация", СсылкаСотрудник.Организация.НаименованиеСокращенное);
Данные.Вставить("ФИОСотрудника", СсылкаСотрудник.Наименование);
// Получаем руководителя организации
ОтветственныеЛица = ОтветственныеЛицаБП.ОтветственныеЛица(СсылкаСотрудник.Организация, ТекущаяДата());
Данные.Вставить("ФИОРуководителя", ФизическиеЛица.ФамилияИнициалыФизЛица(ОтветственныеЛица.Руководитель));
Данные.Вставить("ДолжностьРуководителя", ОтветственныеЛица.РуководительДолжность);
Возврат Данные;
КонецФункции
&НаСервере
Функция ПолучитьМакетНаСервере()
// Получаем макет из метаданных обработки по его имени ("ДвоичныеДанные")
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
Попытка
Возврат ОбработкаОбъект.ПолучитьМакет("ДоговорШаблон");
Исключение
Возврат Неопределено;
КонецПопытки;
КонецФункции
Использование COM-технологий постепенно уходит в прошлое. Этот способ медленный, требует обязательной установки Microsoft Office на компьютерах пользователей, не работает под Linux и часто блокируется политиками безопасности ОС. В современных версиях БСП (3.0 и выше) есть великолепный кроссплатформенный механизм генерации офисных документов, который собирает файлы на стороне сервера напрямую на уровне ZIP-архивов и XML.
Для реализации этого метода нам потребуется:
.docx (обычный файл Word).<Параметр> использовать специальные теги БСП прямо в тексте документа:
{v8 Область.Шапка} ... {/v8 Область.Шапка}.{v8 ФИОСотрудника}, {v8 Организация}.ВызовСерверногоМетода.Посмотрим на пример реализации кода в Модуле объекта нашей внешней печатной формы. Клиентский метод и форма в данном случае вообще не требуются!
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
// 1. Извлекаем макет двоичных данных из тела внешней обработки
ДвоичныеДанныеМакета = ПолучитьМакет("ТрудовойДоговорDOCX");
// 2. Инициализируем макет программными средствами БСП
Макет = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(ДвоичныеДанныеМакета, "DOCX");
// 3. Создаем структуру для будущей печатной формы
ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФормуОфисногоДокумента("DOCX", Макет);
Для Каждого СсылкаИзМассива Из МассивОбъектов Цикл
// Получаем структуру данных сотрудника для заполнения
ДанныеДляЗаполнения = ПолучитьДанныеДляДоговора(СсылкаИзМассива);
// Выбираем область макета "Шапка"
ОбластьШапка = УправлениеПечатью.ОбластьМакета(Макет, "Область.Шапка");
// Присоединяем область и автоматически заполняем параметры
УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьШапка, ДанныеДляЗаполнения);
КонецЦикла;
// 4. Генерируем финальный архив документа во временном хранилище
АдресВоВременномХранилище = УправлениеПечатью.СформироватьДокумент(ПечатнаяФорма);
// 5. Передаем сформированный документ в общую коллекцию печати БСП
УправлениеПечатью.ВывестиОфисныйДокументВКоллекцию(КоллекцияПечатныхФорм,
"ТрудовойДоговор",
"Трудовой договор с сотрудником",
АдресВоВременномХранилище);
// Освобождаем временные файлы и очищаем память
УправлениеПечатью.ОчиститьСсылкиНаМакетыОфисныхДокументов(Макет);
КонецПроцедуры
Функция ПолучитьДанныеДляДоговора(СсылкаСотрудник)
Данные = Новый Структура;
Данные.Вставить("Организация", СсылкаСотрудник.Организация.НаименованиеСокращенное);
Данные.Вставить("ФИОСотрудника", СсылкаСотрудник.Наименование);
ОтветственныеЛица = ОтветственныеЛицаБП.ОтветственныеЛица(СсылкаСотрудник.Организация, ТекущаяДата());
Данные.Вставить("ФИОРуководителя", ФизическиеЛица.ФамилияИнициалыФизЛица(ОтветственныеЛица.Руководитель));
Данные.Вставить("ДолжностьРуководителя", ОтветственныеЛица.РуководительДолжность);
Возврат Данные;
КонецФункции
При использовании этого серверного метода БСП самостоятельно упакует сформированный DOCX-файл, передаст его на сторону клиента и откроет у пользователя в той офисной программе, которая установлена по умолчанию для документов этого расширения (MS Word, LibreOffice, МойОфис). Это полностью решает проблему зависания фонового процесса WINWORD.EXE и гарантирует быструю и отказоустойчивую работу печатной формы в любых операционных системах.