ДЛЯ ЧЕГО/КОМУ ЭТО НУЖНО:
Если в вашей компании есть некий корпоративный стиль, дизайнеры и их не устраивает как выглядят ваши отчеты сверстаные на табличных документах в 1с - они могут вам предложить использовать другой выходной формат. Первыми на ум приходит pdf, html и word.
- pdf макеты не очень удобны в редактировании.
- html это скорее для вебсервисов и отчетов на почту.
- Остается word.
Эта обработка призвана послужить шаблоном для создания подключаемой печатной формы с участием docx макета (внимание, просто ".doc" - обладает совершенно отличным, бинарным, протоколом и в статье не рассматривается), при этом не используя COMОбъект("Word.Application").
Она может может выдавать Word документ ко всем выделенным через CTRL || SHIFT объектам. Параметры задаются на макете довольно просто - выделяются определенным цветом, а также потдерживается обращение через точку прям в макете: Сотрудник.ФизическоеЛицо.Фамилия (в будующем планирую добавить возможность склонения и форматирования параметра прям в макете). В обработку без изменений включена функция daСклонение, за что огромное спасибо её создателю - Михаилу Андропову, её создателю.
ЕСЛИ ЛЕНЬ ЧИТАТЬ И НАДО ДЕЛАТЬ:
- Изменяете имя и синоним обработки
- Изменяете тип данных реквизита СсылкаНаОбъект
- Заменяете макет макет_docx своим docx макетом, где фон параметров выделен цветом RED
- В модуле объекта редактируете процедуру ПолучитьПараметрыЗаполнения под себя (возвращает структуру, где ключи соответсвуют названиям параметров в макете.
- Сохраняете, пользуете, you are looking sweet now *__*
ЧТО К ЧЕМУ:
В обработке, в модуле объекта есть две процедуры и функции, с которых следует начать её рассмотрение
- ПолучитьИнформациюОбОбработке
Функция возвращает структуру со всеми необходимыми значениями для подключения её как дополнительной обработки.
Все данные берутся из метаданных обработки (Имя, Синоним, Комментарий), Назначение обработки берется из массива типов реквизита СсылкаНаОбъект (в публикации это Справочник.Сотрудники и Документ.Увольнение)
- ПолучитьПараметрыЗаполнения
Функция на входе получает ссылку на объект отправленный на печать, а возвращает структуру со всеми необходимыми параметрами для заполнения макета (Ключ значения в структуре должен соответствовать параметру в макете). По сути именно в этой функции вы делаете необходимые запросы для заполнения результирующей структуры, которая будет выведена на печать.
Как работает формирование документа после вызова на печать:
- Типовой механизм БСП получает информацию об обработке из экспортной функции СведенияОВнешнейОбработке()
- Запускает на клиенте в модуле основной формы экспортную процедуру Печать
- Процедура обращается на сервер и запускает функцию модуля объекта СформироватьДокументы (возвращает массив двоичных данных для сохранения на клиенте)
- Получаем данные для заполнения печатной формы из функции ПолучитьПараметрыЗаполнения
- Начинаем заполнение макета в функции возвращающей двоичные данные и имя документа - ЗаполнитьМакет
- Извлекаем макет в папку с временными файлами (разархивируя docx на составные xml файлы)
- Перебираем полученные xml файлы в поисках параметров, разбирая DOM с помощью разыменователя и выражения XPath "descendant::w:r/w:rPr/w:highlight[@w:val='{ВАШ ЦВЕТ}']/../../w:t"
- Найденные узлы с параметрами макета заполняем из созданой ранее структуры с параметрами.
- Собираем назад docx документ из xml файлов.
- Отдаем данные на клиент, сохраняем во временные файлы, открываем.
Возможно это звучит несколько запутано, но по факту вам требуется лишь сделать необходимые запросы в функции ПолучитьПараметрыЗаполнения и вернуть результат в структуре с параметры совпадающими с параметрами макета. Параметры макета должны быть выделены цветом (в моем случае это RED, ну или другим удобным вам способом, в этом случае измените выражение XPath).
Весь код открыт, пока разрабатывалось только под управляемые формы, но не вижу проблем переделать под legacy.
Внешних зависимостей быть не должно, испытывалось на версии 8.3.10, по сути должно работать из более ранней, но Я большой любитель СтрШаблон() и СтрРазделить() - в будующем постараюсь заменить их на скопированные из общего модуля БСП СтроковыеФункцииКлиентСервер.
UPD: К сожалению Инфостарт не дает бесплатно выложить обработку, поэтому прикрепляю листинг (ну или напишите мне):
//////////////////////////////////////////////////////////////
#Область Объявление_констант
#КонецОбласти
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
#Область При_открытии
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ЗаполнитьПоУмолчанию();
КонецПроцедуры // ПриСозданииНаСервере()
&НаСервере
Процедура ЗаполнитьПоУмолчанию()
Попытка
Запрос = Новый Запрос(СтрШаблон(
"ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1 Ссылка ИЗ %1"
, Метаданные.НайтиПотипу(РеквизитФормыВЗначение("Объект").Метаданные().Реквизиты.СсылкаНаОбъект.Тип.Типы()[0]).ПолноеИмя()));
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Количество() Тогда Возврат КонецЕсли;
Выборка.Следующий();
Объект.СсылкаНаОбъект = Выборка.Ссылка;
Исключение
КонецПопытки;
КонецПроцедуры // ЗаполнитьПоУмолчанию()
#КонецОбласти
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
#Область Сформировать_документ
&НаКлиенте
Процедура Печать(ИдентификаторКоманды, МассивОбъектов = Неопределено) Экспорт
Если ТипЗнч(ИдентификаторКоманды) = Тип("КомандаФормы") И (Объект.СсылкаНаОбъект = Неопределено Или Объект.СсылкаНаОбъект.Пустая()) Тогда
// Если Печать вызвана из формы обработки, но СсылкаНаОбъект не заполнена
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Заполните данный реквизит.";
Сообщение.Поле = "Объект.СсылкаНаОбъект";
Сообщение.УстановитьДанные(Объект.СсылкаНаОбъект);
Сообщение.Сообщить();
Возврат;
ИначеЕсли ТипЗнч(ИдентификаторКоманды) = Тип("КомандаФормы") Тогда
// Если Печать вызвана из формы обработки
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(Объект.СсылкаНаОбъект);
ИначеЕсли ТипЗнч(МассивОбъектов) = Тип("Массив") И МассивОбъектов.Количество() Тогда
// Если Печать вызвана из модуля объекта (подключаемая печатная форма)
Иначе
// Такого быть не должно
ВызватьИсключение "Команда формы ""Печать"" вызвана не из модуля объекта и не по кнопке.";
Возврат;
КонецЕсли;
МассивДокументов = ПечатьНаСервере(МассивОбъектов);
Если ТипЗнч(МассивДокументов) <> Тип("Массив") Или Не МассивДокументов.Количество() Тогда Возврат КонецЕсли;
ВыбратьКаталогЗаписиФайлов(МассивДокументов);
КонецПроцедуры // Печать()
&НаСервере
Функция ПечатьНаСервере(МассивОбъектов) Экспорт
МодульОбъекта = РеквизитФормыВЗначение("Объект");
Возврат МодульОбъекта.СформироватьДокументы(МассивОбъектов);
КонецФункции // ПечатьНаСервере()
// ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~
#Область Сохранить_файлы_на_диск
&НаКлиенте
Процедура ВыбратьКаталогЗаписиФайлов(МассивДокументов)
ДополнительныеПараметры = Новый Структура("МассивДокументов", МассивДокументов);
ВыбранныеКаталоги = Новый Массив;
ВыбранныеКаталоги.Добавить(КаталогВременныхФайлов());
// Вариант сохраненния во "временные файлы" (Папка %temp% в Windows)
СохранитьФайлыВКаталог(ВыбранныеКаталоги, ДополнительныеПараметры);
Возврат;
// Вариант выбора каталога пользователем
Режим = РежимДиалогаВыбораФайла.ВыборКаталога;
ДиалогВыбораКаталога = Новый ДиалогВыбораФайла(Режим);
ДиалогВыбораКаталога.Заголовок = СокрЛП("Выберите путь для сохранения файл" + ?(МассивДокументов.Количество()>1, "ов", "а"));
ДиалогВыбораКаталога.МножественныйВыбор = Ложь;
ДиалогВыбораКаталога.ПолноеИмяФайла = "";
ДиалогВыбораКаталога.Каталог = "";
ДиалогВыбораКаталога.Расширение = "";
ОписаниеОповещения = Новый ОписаниеОповещения("СохранитьНаДискФайлЗаписьФайла"
, ЭтаФорма
, ДополнительныеПараметры);
ДиалогВыбораКаталога.Показать(ОписаниеОповещения);
КонецПроцедуры // ВыбратьКаталогЗаписиФайлов()
&НаКлиенте
Процедура СохранитьФайлыВКаталог(ВыбранныеКаталоги, ДополнительныеПараметры) Экспорт
Если ТипЗнч(ВыбранныеКаталоги) <> Тип("Массив")
Или ВыбранныеКаталоги.Количество() <> 1
Или ТипЗнч(ДополнительныеПараметры) <> Тип("Структура")
Или Не ДополнительныеПараметры.Свойство("МассивДокументов")
Или ТипЗнч(ДополнительныеПараметры.МассивДокументов) <> Тип("Массив")
Или Не ДополнительныеПараметры.МассивДокументов.Количество()
Тогда Возврат КонецЕсли;
Для Каждого Документ Из ДополнительныеПараметры.МассивДокументов Цикл
Если Не Документ.Свойство("ДД")
Или Не Документ.Свойство("ИмяДокумента")
Или ТипЗнч(Документ.ДД) <> Тип("ДвоичныеДанные")
Или ТипЗнч(Документ.ИмяДокумента) <> Тип("Строка")
Или ПустаяСтрока(Документ.ИмяДокумента)
Тогда Продолжить КонецЕсли;
ПолныйПутьФайла = СтрЗаменить(ВыбранныеКаталоги[0] + "\\" + Документ.ИмяДокумента, "\\", "\");
Документ.ДД.Записать(ПолныйПутьФайла);
ЗапуститьПриложение(ПолныйПутьФайла); // Если требуется - открываем документы после сохранения
КонецЦикла; // Для Каждого Документ Из ДополнительныеПараметры.МассивДокументов
// ЭтаФорма.Закрыть(Истина);
КонецПроцедуры // СохранитьФайлыВКаталог()
#КонецОбласти
// ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~ ~~~~~
#КонецОбласти
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
#Область Заполнение_констант
#КонецОбласти
//////////////////////////////////////////////////////////////
#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда
//////////////////////////////////////////////////////////////
#Область Объявление_переменных
Перем ЦветВыделенияПараметров;
#КонецОбласти
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
#Область Основная_информация_об_обработке
// ОСНОВНАЯ ИНФОРМАЦИЯ НАЧАЛО
Функция ПолучитьИнформациюОбОбработке() Экспорт
Инф = Новый Структура;
// Наименование обработки (имя для регистрации в справочнике внешних отчетов и обработок)
Инф.Вставить("Наименование", Метаданные().Синоним);
// Идентификатор без пробелов и знаков препинания
Инф.Вставить("Идентификатор", Метаданные().Имя);
// Дополнительная информация
Инф.Вставить("Информация", СтрЗаменить(СтрШаблон("%1 %2 (%3 г.)", Метаданные().Синоним, Метаданные().Комментарий, Формат(ТекущаяДата(), "ДФ='d MMM yyyy'")), " ", " "));
// Версия для удобства версирования (1.0, 1.1, и т.д.)
Инф.Вставить("Версия", Метаданные().Комментарий);
// Варианты: "ДополнительнаяОбработка", "ДополнительныйОтчет", "ЗаполнениеОбъекта", "Отчет", "ПечатнаяФорма", "СозданиеСвязанныхОбъектов"
Инф.Вставить("Вид", "ПечатнаяФорма");
// в безопасном режиме не доступна COM технология, загрузка внешних компонент, доступ к файловой системе, доступ к Интернету.
Инф.Вставить("БезопасныйРежим", Ложь);
// Использование. Варианты: "ОткрытиеФормы", "ВызовКлиентскогоМетода", "ВызовСерверногоМетода"
Инф.Вставить("Использование", "ВызовКлиентскогоМетода");
// Показывать оповещение. Варианты Истина, Ложь
Инф.Вставить("Оповещение", Истина);
// Модификатор
Инф.Вставить("Модификатор", "");
// Указываем назначение к которому делаем внешнюю печ. форму
МассивНазначений = Новый Массив;
УказанныеТипы = ЭтотОбъект.Метаданные().Реквизиты.СсылкаНаОбъект.Тип.Типы(); // Возьмем типы из реквизита "СсылкаНаОбъект"
Для Каждого УказанныйТип ИЗ УказанныеТипы Цикл
ОбъектМетаданных = Метаданные.НайтиПотипу(УказанныйТип);
Если ОбъектМетаданных = Неопределено Тогда Продолжить; КонецЕсли;
МассивНазначений.Добавить(ОбъектМетаданных.ПолноеИмя());
КонецЦикла;
Инф.Вставить("Назначение", МассивНазначений);
Возврат Инф;
КонецФункции // ПолучитьИнформациюОбОбработке()
// ОСНОВНАЯ ИНФОРМАЦИЯ КОНЕЦ
// ПОДГОТОВКА РЕГИСТРАЦИИ НАЧАЛО
// ВСЕ ДАННЫЕ ПО РЕГИСТРАЦИИ ЗАПОЛНЯЮТСЯ В ФУНКЦИИ ПолучитьИнформациюОбОбработке()
Функция СведенияОВнешнейОбработке() Экспорт
Инф = ПолучитьИнформациюОбОбработке();
ТаблицаКоманд = ПолучитьТаблицуКоманд();
ПараметрыРегистрации = Новый Структура;
МассивНазначений = Новый Масси