Разработка внешних печатных форм на основе офисных документов Word (в форматах DOC/DOCX) — стандартная задача в экосистеме 1С, особенно при работе с кадровыми документами в конфигурациях вроде «1С:Зарплата и управление персоналом». В процессе реализации таких механизмов через Библиотеку Стандартных Подсистем (БСП) начинающие разработчики часто сталкиваются с двумя неприятными проблемами: вместо заполненного бланка на экран выводится абсолютно пустой документ Word, либо программа аварийно завершает работу с ошибкой «Поле объекта не обнаружено». В этой статье мы подробно разберем причины возникновения этих ошибок и напишем корректный код для их устранения.
Вывод пустого документа Word обычно связан с тем, что механизм печати БСП не получает сам файл шаблона. Давайте проанализируем фрагмент кода, в котором совершается эта ошибка:
&НаСервере
Функция ПолучитьДанныеДляПечатиДопСоглашенияНаОбучение
мОбъект = РеквизитФормыВЗначение("Объект");
ДвоичныеДанные = мОбъект.ПолучитьМакет("ПФ_DOC_ДопСоглашениеНаОбучение");
Возврат мОбъект.ПолучитьДанныеДляПечатиДопСоглашенияНаОбучение(ОбъектПечати, ДатаВступленияСоглашенияВСилу);
КонецФункции
Обратите внимание на переменную ДвоичныеДанные. На сервере макет успешно извлекается из объекта, но записывается в локальную переменную функции, которая никак не возвращается на клиентскую сторону. На клиенте в процедуре вывода печати вызывается следующий метод:
Макет = УправлениеПечатьюКлиент.ИнициализироватьМакетОфисногоДокумента(ДвоичныеДанные, ТипМакета, ИмяМакета);
Поскольку на клиенте переменная ДвоичныеДанные не была определена (или равна Неопределено), метод БСП не находит оригинального шаблона и по умолчанию создает абсолютно новый, пустой документ Microsoft Word. Чтобы решить эту проблему, необходимо передавать двоичные данные макета вместе с данными для заполнения в единой структуре.
Изменим логику серверной функции так, чтобы она возвращала структуру, содержащую и данные для заполнения, и сам двоичный макет документа. Рассмотрим правильный вариант реализации:
&НаСервере
Функция ПолучитьДанныеИМакетДляПечати()
мОбъект = РеквизитФормыВЗначение("Объект");
Результат = Новый Структура;
Результат.Вставить("Макет", мОбъект.ПолучитьМакет("ПФ_DOC_ДопСоглашениеНаОбучение"));
Результат.Вставить("Данные", мОбъект.ПолучитьДанныеДляПечатиДопСоглашенияНаОбучение(ОбъектПечати, ДатаВступленияСоглашенияВСилу));
Возврат Результат;
КонецФункции
&НаКлиенте
Процедура Печать(Команда)
Если ДопСоглашениеНаОбучение Тогда
ПакетПечати = ПолучитьДанныеИМакетДляПечати();
НапечататьДопСоглашениеНаОбучение(ПакетПечати.Данные, ПакетПечати.Макет);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура НапечататьДопСоглашениеНаОбучение(ДанныеОбъекта, ДвоичныеДанные)
ТипМакета = "DOC";
ИмяМакета = "Дополнительное соглашение на обучение";
Макет = УправлениеПечатьюКлиент.ИнициализироватьМакетОфисногоДокумента(ДвоичныеДанные, ТипМакета, ИмяМакета);
Если Макет = Неопределено Тогда
Возврат;
КонецЕсли;
Попытка
ПечатнаяФорма = УправлениеПечатьюКлиент.ИнициализироватьПечатнуюФорму(ТипМакета, Макет.НастройкиСтраницыМакета, Макет);
Если ПечатнаяФорма = Неопределено Тогда
УправлениеПечатьюКлиент.ОчиститьСсылки(Макет);
Возврат;
КонецЕсли;
ОписаниеОбласти = Новый Структура("ИмяОбласти, ТипОбласти", "Заголовок", "Общая");
Область = УправлениеПечатьюКлиент.ОбластьМакета(Макет, ОписаниеОбласти);
УправлениеПечатьюКлиент.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, Область, ДанныеОбъекта, Ложь);
УправлениеПечатьюКлиент.ПоказатьДокумент(ПечатнаяФорма);
Исключение
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(КраткоеПредставлениеОшибки(ИнформацияОбОшибке));
КонецПопытки;
КонецПроцедуры
Эта ошибка часто возникает из-за невнимательности при работе с коллекциями данных. Метод КадровыйУчет.КадровыеДанныеСотрудников возвращает не структуру и не конкретную строку, а коллекцию — ТаблицаЗначений. Рассмотрим ошибочный фрагмент кода:
КадровыеДанные = КадровыйУчет.КадровыеДанныеСотрудников(Истина, Сотрудник, "Должность, Пол, ФамилияИО");
Если КадровыеДанные.Количество > 0 Тогда
СтрокаКадровыеДанные = КадровыеДанные[0];
ЗаполнитьДанныеСотрудника(Структура, СтрокаКадровыеДанные);
Структура.Вставить("ИменуемаяЫЙ", ?(КадровыеДанные.Пол = Перечисления.ПолФизическогоЛица.Мужской, "именуемый", "именуемая"));
КонецЕсли;
В данном фрагменте допущены сразу три критические ошибки:
Количество у таблицы значений является методом. Проверка должна выглядеть как КадровыеДанные.Количество() > 0.КадровыеДанные.Пол разработчик пытается получить значение колонки «Пол» напрямую из таблицы значений. Таблица значений не хранит данные полей в корне своего объекта — обращаться нужно к конкретной извлеченной строке СтрокаКадровыеДанные.Пол.{v8 Пол} частями или исправляли опечатки, текстовый процессор мог разбить эту строку внутри файла на несколько XML-тегов форматирования. В таком случае БСП не сможет распознать этот параметр как единое целое.Чтобы добавить новые параметры (например, номер и дату трудового договора сотрудника) и избежать ошибок при определении пола сотрудника, перепишем функцию получения данных следующим образом:
Функция ПолучитьДанныеДляПечатиДопСоглашенияНаОбучение(Сотрудник, ДатаВступленияСоглашенияВСилу) Экспорт
Организация = Сотрудник.ГоловнаяОрганизация;
Структура = Новый Структура;
// Форматируем дату соглашения для вывода красивым текстом
Структура.Вставить("ДатаВступленияСоглашенияВСилу", Формат(ДатаВступленияСоглашенияВСилу, "ДФ='''""''дд''""'' ММММ гггг ''года'''"));
ЗаполнитьДанныеОрганизации(Структура, Организация);
// Запрашиваем необходимые кадровые данные, включая пол и сведения о трудовом договоре
СписокПолей = "Организация, НомерДоговораКонтракта, ДатаДоговораКонтракта, АдресПоПропискеПредставление, ФИОПолные, Пол, ФамилияИО, ДокументПредставление";
КадровыеДанные = КадровыйУчет.КадровыеДанныеСотрудников(Истина, Сотрудник, СписокПолей);
Если КадровыеДанные.Количество() > 0 Тогда
СтрокаКадровыеДанные = КадровыеДанные[0];
// Передаем в процедуру заполнения именно строку таблицы кадровых данных
ЗаполнитьДанныеСотрудника(Структура, СтрокаКадровыеДанные);
// Обращаемся к реквизиту Пол через СтрокаКадровыеДанные
Структура.Вставить("ИменуемаяЫЙ", ?(СтрокаКадровыеДанные.Пол = Перечисления.ПолФизическогоЛица.Мужской, "именуемый", "именуемая"));
// Безопасное заполнение номера договора
Если ЗначениеЗаполнено(СтрокаКадровыеДанные.НомерДоговораКонтракта) Тогда
Структура.Вставить("НомерДоговора", СтрокаКадровыеДанные.НомерДоговораКонтракта);
Иначе
Структура.Вставить("НомерДоговора", "___________");
КонецЕсли;
// Безопасное заполнение даты договора
Если ЗначениеЗаполнено(СтрокаКадровыеДанные.ДатаДоговораКонтракта) Тогда
Структура.Вставить("ДатаДоговора", Формат(СтрокаКадровыеДанные.ДатаДоговораКонтракта, "ДФ=dd.MM.yyyy"));
Иначе
Структура.Вставить("ДатаДоговора", "___________");
КонецЕсли;
КонецЕсли;
Возврат Структура;
КонецФункции
Процедура ЗаполнитьДанныеСотрудника(Структура, КадровыеДанные)
Структура.Вставить("СотрудникФИО", КадровыеДанные.ФамилияИО);
Структура.Вставить("СотрудникАдрес", КадровыеДанные.АдресПоПропискеПредставление);
КонецПроцедуры
Чтобы добавленные нами параметры успешно вывелись в файл, выполните следующие действия в самом шаблоне Microsoft Word:
{v8 НомерДоговора} и {v8 ДатаДоговора}.