Перенос программного кода из клиентских процедур на сторону сервера — стандартная задача при оптимизации и переходе 1С:Предприятия на клиент-серверную архитектуру. Однако разработчики часто сталкиваются с серьезной проблемой: привычные механизмы формирования офисных документов через COM-соединение (например, COMОбъект("Word.Application")) или использование объектов типа Active Document напрямую на сервере либо вызывают критические ошибки выполнения, либо приводят к нестабильной работе всей системы.
В этой статье мы подробно разберем, почему старый «клиентский» подход дает сбои на сервере, как исправить популярную ошибку передачи данных XDTO и рассмотрим три практических метода реализации серверной печати шаблонов MS Word: от классического гибридного сценария до современного кроссплатформенного механизма на базе БСП.
Попытка вызвать клиентский код работы с MS Word на стороне сервера 1С (особенно в контексте выполнения фоновых заданий или серверных вызовов управляемых форм) неизбежно приводит к ошибкам. Выделим три ключевые причины:
Active Document жестко привязаны к конкретному адресному пространству операционной системы и не могут быть преобразованы в XML, платформа генерирует исключение: «Ошибка преобразования данных XDTO: Значение недопустимого типа».Word.Application на нем физически невозможно.USR1CV83) заблокирован политиками безопасности Windows. Попытки обойти это ограничение через утилиту dcomcnfg часто приводят к тому, что процессы WINWORD.EXE «зависают» в памяти сервера, постепенно перегружая оперативную память и процессор, что в конечном итоге вызывает падение рабочих процессов сервера 1С.Давайте выясним, как правильно обойти эти ограничения с помощью различных подходов.
---
Если в вашей организации все пользователи работают исключительно под управлением Windows и на их локальных компьютерах установлен Microsoft Office, самым быстрым и простым способом адаптации существующего кода является гибридный метод.
При таком подходе сервер 1С отвечает только за выборку данных из базы данных и получение двоичного макета из метаданных конфигурации. Сам процесс генерации файла, запуск MS Word и заполнение полей происходят на стороне клиента. Это исключает нагрузку на сервер и решает проблему зависания процессов WINWORD.EXE.
Разберем по шагам программную реализацию этого метода. Нам понадобятся две процедуры: одна серверная (для получения макета в виде двоичных данных и помещения его во временное хранилище) и одна клиентская (для получения файла из хранилища и заполнения параметров через COM).
Рассмотрим пример кода:
&НаСервере
Функция ПолучитьШаблонИзМакетНаСервере() Экспорт
// Получаем макет типа "Двоичные данные" (или Active Document) из метаданных.
// Обратите внимание: мы не открываем документ на сервере, а получаем его как двоичные данные.
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
МакетДвоичныеДанные = ОбработкаОбъект.ПолучитьМакет("ПФ_DOCX_ШаблонПриказа");
// Помещаем двоичные данные во временное хранилище.
// Адрес временного хранилища - это простая строка, которая легко передается на клиент.
Возврат ПоместитьВоВременноеХранилище(МакетДвоичныеДанные, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура СформироватьИОткрытьДокументWord(Команда)
// Шаг 1. Получаем адрес файла во временном хранилище
АдресХранилища = ПолучитьШаблонИзМакетНаСервере();
// Шаг 2. Создаем временный файл на локальном компьютере пользователя
ПутьКВременномуФайлу = ПолучитьИмяВременногоФайла("docx");
ДвоичныеДанныеМакета = ПолучитьИзВременногоХранилища(АдресХранилища);
ДвоичныеДанныеМакета.Записать(ПутьКВременномуФайлу);
// Шаг 3. Инициализируем COM-объект на клиенте
Попытка
WordПриложение = Новый COMОбъект("Word.Application");
Исключение
ПоказатьПредупреждение(, "Не удалось запустить Microsoft Word. Убедитесь, что приложение установлено.");
Возврат;
КонецПопытки;
// Шаг 4. Открываем наш временный файл-шаблон
WordДокумент = WordПриложение.Documents.Add(ПутьКВременномуФайлу);
// Отключаем видимость на время заполнения для ускорения работы
WordПриложение.Visible = Ложь;
// Шаг 5. Пример заполнения одиночных параметров (закладок или переменных)
Попытка
// Заполнение через механизм переменных документа (Variables)
WordДокумент.Variables("OrgName").Value = "ООО \"Рога и Копыта\"";
WordДокумент.Variables("DocumentNumber").Value = "45-к/2023";
WordДокумент.Variables("DocumentDate").Value = Формат(ТекущаяДата(), "ДЛФ=DD");
// Обновляем поля в документе, чтобы переменные отобразились в тексте
WordДокумент.Fields.Update();
Исключение
Сообщить("Ошибка при заполнении параметров заголовка документа: " + ОписаниеОшибки());
КонецПопытки;
// Шаг 6. Работа с табличной частью документа Word
// Предположим, что в шаблоне Word уже создана пустая таблица (минимум 1 строка-шапка и 1 строка-шаблон)
Если WordДокумент.Tables.Count > 0 Тогда
WordТаблица = WordДокумент.Tables(1); // Индексация в COM Word начинается с 1
// Получаем данные для таблицы с сервера (например, структуру или таблицу значений)
ДанныеДляТаблицы = ПолучитьДанныеТабличнойЧастиНаСервере();
ТекущаяСтрокаДокумента = 2; // Предполагаем, что первая строка - это шапка таблицы
НомерПП = 1;
Для Каждого СтрокаДанных Из ДанныеДляТаблицы Цикл
// Если это не первая строка данных, добавляем новую строку в таблицу Word
Если НомерПП > 1 Тогда
WordТаблица.Rows.Add();
КонецЕсли;
// Заполняем ячейки таблицы
WordТаблица.Cell(ТекущаяСтрокаДокумента, 1).Range.Text = Строка(НомерПП);
WordТаблица.Cell(ТекущаяСтрокаДокумента, 2).Range.Text = СтрокаДанных.СотрудникФИО;
WordТаблица.Cell(ТекущаяСтрокаДокумента, 3).Range.Text = СтрокаДанных.Должность;
WordТаблица.Cell(ТекущаяСтрокаДокумента, 4).Range.Text = Формат(СтрокаДанных.Оклад, "ЧДЦ=2");
ТекущаяСтрокаДокумента = ТекущаяСтрокаДокумента + 1;
НомерПП = НомерПП + 1;
КонецЦикла;
КонецЕсли;
// Шаг 7. Показываем готовый документ пользователю
WordПриложение.Visible = Истина;
WordПриложение.Activate();
// Очищаем временный файл (необязательно, но рекомендуется для поддержания чистоты диска)
// Внимание: удалять файл нужно только после закрытия Word пользователем, либо доверить это операционной системе.
КонецПроцедуры
Этот подход полностью решает проблему ошибки передачи данных XDTO, так как COM-объект создается исключительно на стороне клиента, а между сервером и клиентом передаются только бинарные данные шаблона.
---
Если перед вами стоит задача сделать печать полностью серверной, независимой от наличия установленного Microsoft Office на сервере или операционной системы (Windows/Linux), необходимо использовать механизмы Библиотеки Стандартных Подсистем (БСП). При необходимости автоматизировать формирование документов без сложного программирования можно использовать платформу One RPA для формирования документов и роботизации действий пользователя.
БСП предоставляет готовую подсистему «Печать», которая умеет парсить файлы шаблонов форматов .docx (MS Word) и .odt (LibreOffice) прямо на сервере. Эти файлы физически представляют собой ZIP-архивы, содержащие разметку XML. Платформа 1С распаковывает их, выполняет замену параметров и собирает обратно в готовый файл. MS Word на сервере для этого не нужен.
Вам необходимо создать файл в MS Word и разметить его специальными тегами БСП:
{v8 ИмяПараметра}. Например: {v8 НомерДоговора}. При этом оформление шрифта (жирный, курсив, размер) применится к выводимому значению автоматически.{v8 Область.ИмяОбласти}
{/v8 Область.ИмяОбласти}
Проанализируем пример кода формирования печатной формы на сервере с использованием общего модуля БСП УправлениеПечатью:
&НаСервере
Функция СформироватьДокументНаСервереБСП(СсылкаНаОбъект)
// 1. Получаем двоичные данные макета из метаданных нашей конфигурации
ДвоичныеДанныеМакета = УправлениеПечатью.МакетПечатнойФормы("Обработка.ПечатьПриказов.ПФ_DOCX_ШаблонПриказа");
// 2. Инициализируем макет офисного документа средствами БСП
// Метод определяет тип макета (docx или odt) и подготавливает структуру для заполнения
Макет = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(ДвоичныеДанныеМакета, "DOCX");
// 3. Создаем структуру печатной формы
ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФормуОфисногоДокумента("DOCX");
// 4. Заполняем Шапку документа
// Получаем область шапки из макета
ОбластьШапка = УправлениеПечатью.ОбластьОфисногоДокумента(Макет, "Область.Шапка");
// Создаем структуру с данными для заполнения параметров шапки
ДанныеПараметровШапки = Новый Структура;
ДанныеПараметровШапки.Вставить("Организация", СсылкаНаОбъект.Организация.Наименование);
ДанныеПараметровШапки.Вставить("НомерПриказа", СсылкаНаОбъект.Номер);
ДанныеПараметровШапки.Вставить("ДатаПриказа", Формат(СсылкаНаОбъект.Дата, "ДЛФ=DD"));
// Присоединяем заполненную область к нашей печатной форме
УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьШапка, ДанныеПараметровШапки);
// 5. Заполняем табличную часть (Область строки таблицы)
ОбластьСтроки = УправлениеПечатью.ОбластьОфисногоДокумента(Макет, "Область.СтрокаТаблицы");
НомерСтроки = 1;
Для Каждого ТекСтрока Из СсылкаНаОбъект.Сотрудники Цикл
ДанныеПараметровСтроки = Новый Структура;
ДанныеПараметровСтроки.Вставить("НомерПП", НомерСтроки);
ДанныеПараметровСтроки.Вставить("ФИО", ТекСтрока.Сотрудник.Наименование);
ДанныеПараметровСтроки.Вставить("Должность", ТекСтрока.Должность.Наименование);
ДанныеПараметровСтроки.Вставить("Оклад", Формат(ТекСтрока.Оклад, "ЧДЦ=2; ЧГ=0"));
// Присоединяем каждую заполненную строку к общей таблице в документе
УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьСтроки, ДанныеПараметровСтроки);
НомерСтроки = НомерСтроки + 1;
КонецЦикла;
// 6. Заполняем Подвал документа
ОбластьПодвал = УправлениеПечатью.ОбластьОфисногоДокумента(Макет, "Область.Подвал");
ДанныеПодвала = Новый Структура;
ДанныеПодвала.Вставить("Руководитель", СсылкаНаОбъект.Организация.Руководитель.Наименование);
УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьПодвал, ДанныеПодвала);
// 7. Собираем итоговые двоичные данные заполненного документа DOCX
ДанныеГотовогоДокумента = УправлениеПечатью.СформироватьДокумент(ПечатнаяФорма);
// Помещаем готовый файл во временное хранилище, чтобы передать на клиент
Возврат ПоместитьВоВременноеХранилище(ДанныеГотовогоДокумента, УникальныйИдентификатор);
КонецФункции
Для открытия сформированного документа на клиенте используется простой код:
&НаКлиенте
Процедура НапечататьДокументБСП(Команда)
// Получаем заполненный DOCX-файл с сервера
АдресХранилища = СформироватьДокументНаСервереБСП(Объект.Ссылка);
// Сохраняем его во временную директорию клиента и запускаем ассоциированное приложение
ПутьКФайлу = ПолучитьИмяВременногоФайла("docx");
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
ДвоичныеДанные.Записать(ПутьКФайлу);
// Открываем файл в локальном текстовом редакторе пользователя (MS Word, LibreOffice, OnlyOffice)
ЗапуститьПриложение(ПутьКФайлу);
КонецПроцедуры
Данный подход является наиболее отказоустойчивым, современным и безопасным для серверной архитектуры 1С.
---
Рассмотрим ситуацию, когда ваша конфигурация полностью самописная, подсистемы БСП в ней отсутствуют, а ставить тяжелую библиотеку только ради одной печатной формы нецелесообразно. В этом случае мы можем использовать метод прямого редактирования XML-структуры DOCX-файла.
Как мы уже выяснили, файл .docx — это ZIP-архив. Основное текстовое содержимое документа хранится внутри архива по пути word/document.xml. Алгоритм действий на сервере выглядит следующим образом:
ЧтениеZipФайла.word/document.xml в переменную.СтрЗаменить(), заменяем заранее заготовленные текстовые маркеры (например, [НомерПриказа], [Организация]) на актуальные данные из 1С.word/document.xml..docx с помощью объекта 1С ЗаписьZipФайла.Рассмотрим схематичный пример реализации данного алгоритма:
&НаСервере
Функция СформироватьDocxМетодомЗаменыНаСервере(СсылкаНаОбъект)
// Получаем макет двоичных данных шаблона
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
ДвоичныеДанныеШаблона = ОбработкаОбъект.ПолучитьМакет("ПФ_DOCX_Шаблон");
// Определяем пути для работы с файлами на сервере
КаталогРаспаковки = КаталогВременныхФайлов() + "docx_unpack_" + НоваяУникальнаяСтрока() + "\";
ИмяФайлаАрхива = КаталогВременныхФайлов() + "temp_shablon.zip";
// Записываем макет как ZIP-архив
ДвоичныеДанныеШаблона.Записать(ИмяФайлаАрхива);
// Распаковываем архив
Архив = Новый ЧтениеZipФайла(ИмяФайлаАрхива);
Архив.ИзвлечьВсе(КаталогРаспаковки, РежимВосстановленияПутейФайловZIP.Восстанавливать);
Архив.Закрыть();
// Путь к основному XML-файлу с текстом документа
ПутьКXML = КаталогРаспаковки + "word/document.xml";
// Читаем содержимое XML в строку
ТекстовыйДокумент = Новый ТекстовыйДокумент;
ТекстовыйДокумент.Прочитать(ПутьКXML, КодировкаТекста.UTF8);
ТекстXML = ТекстовыйДокумент.ПолучитьТекст();
// Выполняем замены параметров
ТекстXML = СтрЗаменить(ТекстXML, "[Организация]", СсылкаНаОбъект.Организация.Наименование);
ТекстXML = СтрЗаменить(ТекстXML, "[Номер]", СсылкаНаОбъект.Номер);
ТекстXML = СтрЗаменить(ТекстXML, "[Дата]", Формат(СсылкаНаОбъект.Дата, "ДЛФ=D"));
// Сохраняем измененный XML
ТекстовыйДокумент.УстановитьТекст(ТекстXML);
ТекстовыйДокумент.Записать(ПутьКXML, КодировкаТекста.UTF8);
// Запаковываем обратно в DOCX
ИмяГотовогоФайла = КаталогВременныхФайлов() + "ready_document.docx";
НовыйАрхив = Новый ЗаписьZipФайла(ИмяГотовогоФайла);
НовыйАрхив.Добавить(КаталогРаспаковки + "*", РежимСохраненияПутейZIP.СохранятьОтносительныеПути, РежимОбработкиПодкаталоговZIP.Рекурсивно);
НовыйАрхив.Записать();
// Читаем готовый файл в двоичные данные для отправки на клиент
ГотовыеДвоичныеДанные = Новый ДвоичныеДанные(ИмяГотовогоФайла);
// Очищаем временные файлы на сервере
УдалитьФайлы(КаталогРаспаковки);
УдалитьФайлы(ИмяФайлаАрхива);
УдалитьФайлы(ИмяГотовогоФайла);
Возврат ПоместитьВоВременноеХранилище(ГотовыеДвоичныеДанные, УникальныйИдентификатор);
КонецФункции
Этот метод работает чрезвычайно быстро, не требует установленного офисного ПО ни на клиенте, ни на сервере, однако имеет один важный минус: в нем достаточно сложно реализовывать динамическое добавление строк в таблицы, так как для этого придется программно генерировать сложные XML-блоки тегов <w:tr> и <w:tc>.
---
Для того чтобы облегчить выбор архитектурного решения, проанализируем преимущества и недостатки каждого метода:
| Критерий сравнения | Метод 1: Гибридный (Клиентский COM) | Метод 2: БСП (Серверный DOCX) | Метод 3: XML-инъекция (Сервер) |
|---|---|---|---|
| Где выполняется формирование | На Клиенте | На Сервере | На Сервере |
| Требования к ПО на Сервере 1С | Отсутствуют | Отсутствуют | Отсутствуют |
| Требования к ПО на Клиенте | Обязательно наличие MS Word | Любой просмотрщик DOCX (или без него) | Любой просмотрщик DOCX (или без него) |
| Поддерживаемые ОС на сервере | Любая (Windows, Linux) | Любая (Windows, Linux) | Любая (Windows, Linux) |
| Сложность вывода таблиц | Низкая (простой обход циклом) | Низкая (через области БСП) | Высокая (ручная сборка XML-тегов) |
| Скорость работы | Низкая (COM-вызовы медленные) | Высокая | Максимальная |
Подведем итоги: Если ваша информационная база построена на базе типовой конфигурации (ЕРП, КА, УТ, БП, ЗУП), лучшим выбором будет использование Метода №2 (БСП). Это гарантирует надежность, быструю работу на сервере и простоту поддержки шаблонов обычными пользователями. К гибридному Методу №1 стоит прибегать только в случае поддержки старых легаси-систем, когда переделка большого объема логики нецелесообразна по бюджету.