Итак, в чем суть моей статьи?
1. Есть сервер (x64), с установленным Office 2016 (x64), есть 1С 8.3.8 также x64 (имеется в виду сервер 1С). Пользователи подключаются как через тонкого клиента, так и через веб клиент.
2. Есть куча типовых договоров и накладных счет-фактур, оформленных (подготовленных) отделом продаж. Там шрифт, абзац и т.д., естественно, все оформлено в Word,Excel 2016 (x64), естественно, нет желания все это рисовать в 1С, а есть желание засунуть в макеты формата ActiveDocument
Решение банальное, вроде бы :), не буду описывать весь механизм, как это делается, как заполняются данные из 1С в макет ActiveDocument - думаю, вы это знаете прекрасно и без меня. Но в ходе работы выяснилось, что не все так гладко в царстве COM объектов, а именно:
1. Как сохранить файл Word,Excel и передать этот файл клиенту (клиент тонкий и веб)?
2. Какой вариант лучше выбрать: ActiveDocument или ДвоичныеДанные? Хотя для меня это звучит примерно как, что выбрать Водку С Пивом или Пиво С Водкой :). Один фиг, надо забить документы данными из 1С и передать их Клиенту.
Ну да ладно пропустим лирику, я выбираю ActiveDocument, не буду описывать весь алгоритм, просто перечислю "подводные" камни и их решение. Все нижеизложенное это мои личные измышления и в никакой мере не претендуют на истину в последней инстанции. Возможно, вы решили эти проблемы или решите по другому.
1. Камень "первый". Не работает метод SaveAs (как для MSWord, так и для MSExcel). При попытке записать ДвоичныеДанные 1С просто вылетает. Смотрим фрагмент листинга:
MSWord = Макет.Получить();
Попытка
Документ = MSWord.Application.Documents(1);
Документ.Activate();
//Далее каким-либо образом получаем данные и заполняем Word-овский документ
//Получим путь во временной директории для сохранения туда файла
ИмяВрем = ПолучитьИмяВременногоФайла(".docx");
Документ.SaveAs(ИмяВрем); //здесь начинается камень
МойДокументВДвоичныхДанных = Новый ДвоичныеДанные(ИмяВрем); //ну а здесь этот камень нафиг выбивает 1С :)
MSWord.Application.Quit();
Ссылочка //infostart.ru/public/407448/ (выдержка из статьи "Как передать документ Word (ActiveDocument или ДвоичныеДанные) с сервера на клиент"
Опытном путём выявлено, что 1С-ка вырубается с ошибкой, если удалить файл, преобразованный в двоичные данные, и попробовать эти двоичные передать на клиент.
На сервере 1С:Предприятия произошла неисправимая ошибка. Приложение будет закрыто
Я пытался побороть эту ошибку, помещая во временное хранилище двоичные данные, переводя двоичные данные в Неопределено, но победить не удалось. Стоит заметить, что временный файл на сервере 1С-ка со временем сама удаляет корректно.
Возможная ошибка на сервере при вызове метода SaveAs:
Ошибка при вызове метода контекста (SaveAs)
Документ.SaveAs(ИмяВрем);
по причине:
Произошла исключительная ситуация (Microsoft Word): Ошибка команды
Мне поначалу эту ошибку не удалось победить, поэтому я стал использовать ДвоичныеДанные. Позже нашёл решение проблемы: необходимо по пути C:\Windows\SysWOW64\config\systemprofile\ и C:\Windows\System32\config\systemprofile\ создать папки Desktop. Туда никаких файлов никто не пишет, похоже, программе важен факт наличия этой папки. Решение нашёл по ссылке: http://devtrainingforum.v8.1c.ru/forum/thread.jsp?id=581998&threadtype=0Спасибо огромное.
Создание папок
C:\Windows\SysWOW64\config\systemprofile\Desktop
C:\Windows\System32\config\systemprofile\Desktop
проблему решило. Тема закрыта.
В чем причина? Причина в том, что код
MSWord = Макет.Получить();
Всегда вызывает экземпляр объекта COM (x32) независимо от того какой разрядности Office установлен. Вы никогда не задумывались, почему в макет ActoveDocument нельзя вставить файлы с расширением docx,xlsx
это можно проверить и через ДиспетчерЗадач, но факт есть факт - макет ActiveDocument вызывает неявно экземпляр COM (x32) и поэтому все дальнейшие манипуляции нужно делать учитывая это особенность.
1. Либо сервер и все ПО должно быть x32. Тогда ничего делать (в смысле переписывать код) не надо
2. Либо переписать код, таким образом
// получаем имя временного файла
ВремФайл = ПолучитьИмяВременногоФайла("doc");
// этот код точно вызовет экземпляр COM нужной разрядности, в нашем случае x64
Word = Новый COMОбъект("Word.Application");
Word.Displayalerts = 0;
ДокументН = Word.Application.Documents.Add();
ДокументН.SaveAs(ВремФайл,0);
Word.Quit();
// далее все по старому
Макет = УправлениеПечатью.МакетПечатнойФормы("Документ.АктПередачиОборудования."+НазваниеМакета);
MSWord = Макет.Получить();
Попытка
Документ = MSWord.Application.Documents(1);
Документ.Activate();
// здесь что-то делаем, заполняем данные
// здесь мы пересохраним наш файл из COM x62 в COM x64
MSWord.Application.Selection.WholeStory();
MSWord.Application.Selection.Copy();
ДокументН = MSWord.Application.Documents.Open(ВремФайл);
ДокументН.Activate();
MSWord.Application.Selection.Paste();
ДокументН.SaveAs(ВремФайл,0);
ДокументН.Close();
MSWord = Неопределено;
Исключение
// Если произойдет ошибка выводятся данные об ошибке и объект закрывается.
Информация = ИнформацияОбОшибке();
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка - "+Информация.Описание+" код ошибки - "+СокрЛП(Информация.ИсходнаяСтрока));
MSWord.Application.Quit();
КонецПопытки;
Думаю, тут все понятно, сначала мы создали экземпляр COM нужной разрядности, создали пустой файл и сохранили во временную папку, далее работает с COM x32, заполняем данными и напоследок копируем содержимое всего документа и сохраняем в ранее подготовленный файл.
Все то же самое, но только для Excel
ВремФайл = ПолучитьИмяВременногоФайла("xls");
Excel = Новый COMОбъект("Excel.Application");
Excel.Displayalerts = 0;
КнигаН = Excel.WorkBooks.Add();
ЛистН = КнигаН.WorkSheets(1);
КнигаН.SaveAs(ВремФайл, -4143);
Excel.Quit();
Макет = УправлениеПечатью.МакетПечатнойФормы("Документ.НакладнаяОборудования."+НазваниеМакета);
MSExcel = Макет.Получить();
КнигаН = MSExcel.Application.Workbooks.Open(ВремФайл);
ЛистН = КнигаН.WorkSheets(1);
Попытка
WBook = MSExcel.Application.Workbooks(1);
Лист = WBook.WorkSheets(1);
Лист.Activate();
// что-то делаем, заполняем данными из 1С
MSExcel.Application.WorkBooks(1).WorkSheets(1).Cells.Copy(ЛистН.Cells);
КнигаН.Save();
КнигаН.Close();
Исключение
// Если произойдет ошибка выводятся данные об ошибке и объект закрывается.
Информация = ИнформацияОбОшибке();
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка - "+Информация.Описание+" код ошибки - "+СокрЛП(Информация.ИсходнаяСтрока));
MSExcel.Application.Quit();
КонецПопытки;
ну вот "первый" камень я решил, на сервере x64 с Office x64, все работает точно как часы, без ошибок и не надо создавать никаких папок и все прочее.
Камень "второй". фрагмент кода
ВремФайл = ПолучитьИмяВременногоФайла("xls");
есть не очень хорошо, потому как идет запись в папку: "c:\Users\ че там....", вообще эта папка всегда в черном списке всех фаерволов, антивирусов и прочее, прочее, хотя бы открыть центр управления безопасностью Word или Excel. Глянем и мы туда
придется возится с этим, иначе есть вероятность появления "странных" ошибок. Поэтому я предлагаю следующее:
1. Открываем Конфигуратор и добавляем новый РегистрСведений
здесь мы будем хранить наши готовые Word, Excel файлы уже заполненные, конечно:
Объект - Тип Документ.Ссылка
НазваниеМакета - Идентификатор макета
ДокументOffice - ХранилищеЗначений, здесь мы и держим наш готовый файл
2. Дописываем выше написанный код следующим образом:
МЗ = РегистрыСведений.ВременноеХранилищеOffice.СоздатьМенеджерЗаписи();
МЗ.Объект = Выборка.Ссылка;
МЗ.НазваниеМакета = НазваниеМакета;
МЗ.Прочитать();
МЗ.Объект = Выборка.Ссылка;
МЗ.НазваниеМакета = НазваниеМакета;
МЗ.ДокументOffice = Новый ХранилищеЗначения(Новый ДвоичныеДанные(ВремФайл));
МЗ.Записать();
УдалитьФайлы(ВремФайл);
Что мы делаем, мы записываем готовый файл в регистр сведений и затем удаляем сам временный файл, решаем проблему "Центра безопасности Word,Excel". Осталось только одно показать этот готовый файл Клиенту (клиент тонкий и веб)
3. Камень "третий" - передача файла клиенту, тут просто выложу весь код, что-то взял из БСП, что-то из Демонстрационная конфигурация "Управляемое приложение", что-то из Инета, но в общем вот код (целиком)
////////////////////////////////////////////////////////////////////////////////
// СЛУЖЕБНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ БСП
&НаСервере
Функция ПолучитьМакет()
ОбъектСсылка = СтруктураДанных.Объект;
НазваниеМакета = СтруктураДанных.НазваниеМакета;
КлючЗаписи = РегистрыСведений.ВременноеХранилищеOffice.СоздатьКлючЗаписи(Новый Структура("Объект,НазваниеМакета",ОбъектСсылка,НазваниеМакета));
//
Адрес = ПолучитьНавигационнуюСсылку(КлючЗаписи,"ДокументOffice");
Возврат Адрес;
КонецФункции //
&НаКлиенте
Процедура ПослеЗапускаПриложения(КодВозврата, ИмяПриложения) Экспорт
; //
КонецПроцедуры
&НаКлиенте
Процедура ПослеПолученияФайлов(ПереданныеФайлы, ДополнительныеПараметры) Экспорт
Если НЕ ПереданныеФайлы=Неопределено Тогда
Для каждого Описание Из ПереданныеФайлы Цикл
ОпПослеЗапускаПриложения = Новый ОписаниеОповещения("ПослеЗапускаПриложения", ЭтотОбъект, Описание.Имя);
НачатьЗапускПриложения(ОпПослеЗапускаПриложения, Описание.Имя);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораКаталога(ВыбранныеФайлы, ИмяКоманды) Экспорт
Если ВыбранныеФайлы = Неопределено Тогда
Возврат;
КонецЕсли;
Каталог = ВыбранныеФайлы[0];
ОбщегоНазначенияВызовСервера.СохранитьРабочийКаталог(Каталог);
Если ИмяКоманды = "Накладная" Тогда
НазваниеМакета = "Накладная"
КонецЕсли;
СтруктураДанных.Вставить("Каталог", Каталог);
ПодключитьОбработчикОжидания("Подключаемый_ПередатьФайлКлиенту",5,Истина);
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьФайлыЧерезРасширение(ИмяКоманды)
ОпПослеВыбораКаталога = Новый ОписаниеОповещения("ПослеВыбораКаталога", ЭтотОбъект, ИмяКоманды);
Каталог = ОбщегоНазначенияВызовСервера.ПолучитьРабочийКаталог();
Если Каталог = Неопределено ИЛИ Каталог = "" Тогда
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога);
Диалог.Заголовок = НСтр("ru = 'Выбор каталога временного хранения файлов'", "ru");
Диалог.Показать(ОпПослеВыбораКаталога);
Иначе
ВыбранныеФайлы = Новый Массив;
ВыбранныеФайлы.Добавить(Каталог);
ВыполнитьОбработкуОповещения(ОпПослеВыбораКаталога, ВыбранныеФайлы);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьПодключениеРасширенияРаботыСФайлами(РасширениеПодключено,ДополнительныеПараметры) Экспорт
Если РасширениеПодключено Тогда
ОткрытьФайлыЧерезРасширение(ДополнительныеПараметры.ИмяКоманды);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура Подключаемый_ПередатьФайлКлиенту()
Адрес = ПолучитьМакет();
Если Адрес<>Неопределено Тогда
ОтключитьОбработчикОжидания("Подключаемый_ПередатьФайлКлиенту");
НомерДокумента = СтруктураДанных.НомерДокумента;
Каталог = СтруктураДанных.Каталог;
НазваниеМакета = СтруктураДанных.НазваниеМакета;
ПутьКфайлу = Каталог+"\"+НазваниеМакета+"_№"+НомерДокумента+".xls";
Описание = Новый ОписаниеПередаваемогоФайла(ПутьКфайлу, Адрес);
ПередаваемыеФайлы = Новый Массив;
ПередаваемыеФайлы.Добавить(Описание);
НачатьПолучениеФайлов(Новый ОписаниеОповещения("ПослеПолученияФайлов", ЭтотОбъект), ПередаваемыеФайлы, "", Ложь);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ВыполнитьПечатьСервер()
ОбъектСсылка = СтруктураДанных.Объект;
НазваниеМакета = СтруктураДанных.НазваниеМакета;
СтруктураДанных.Вставить("НомерДокумента", ОбъектСсылка.Номер);
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(ОбъектСсылка);
Документы.НакладнаяОборудования.ПечатьНакладная(МассивОбъектов,,НазваниеМакета,Истина);
КонецПроцедуры
&НаКлиенте
Процедура Подключаемый_ВыполнитьПечать()
ВыполнитьПечатьСервер();
КонецПроцедуры
// СтандартныеПодсистемы.Печать
&НаКлиенте
Процедура Подключаемый_ВыполнитьКомандуПечати(Команда)
Ссылка = Элементы.Список.ТекущиеДанные.Ссылка;
СтруктураДанных = Новый Структура;
СтруктураДанных.Вставить("Объект", Ссылка);
СтруктураДанных.Вставить("НазваниеМакета", "Накладная");
ПодключитьОбработчикОжидания("Подключаемый_ВыполнитьПечать", 1, Истина);
ОписаниеКоманды = УправлениеПечатьюКлиент.ОписаниеКомандыПечати(Команда.Имя,ИмяФормы);
НачатьУстановкуРасширенияРаботыСФайлами();
НачатьПодключениеРасширенияРаботыСФайлами(Новый ОписаниеОповещения("ОбработатьПодключениеРасширенияРаботыСФайлами",ЭтотОбъект,Новый Структура("ИмяКоманды",ОписаниеКоманды.Идентификатор)));
КонецПроцедуры
// Конец СтандартныеПодсистемы.Печать
Немного пояснений:
1. Во-первых, клиент у нас работает как через Тонкий, так и через Веб режимы, поэтому заранее в свойствах Конфигуратор ставим следующие значения:
Чтобы не было проблем при работе с браузером
2. Используем обработчики ожидания, чтобы избежать проблем с синхронностью вызов (это касается только режим Веб)
3. И последнее, подключаем Расширение для работы с Файлами (помним что в режиме Тонкий клиент, это расширение включено всегда). И через код:
Адрес = ПолучитьНавигационнуюСсылку(КлючЗаписи,"ДокументOffice");
передаем файл Клиенту используя механизм НавигационнаяСсылка, получаем следующие сообщения в браузере (Тонкий само собой работает):
ну вот, кажется, все. Надеюсь, это поможет кому-нибудь...
P.S.
По поводу Word, Excel файлы вставлять в виде ДвоичныхДанные? проблема-то в чем?
1. Мы либо должны вытащить из макета эти ДвоичныеДанные и заполнить данными из 1С и ВНИМАНИЕ снова записать в виде ДвоичныхДанных (Водка С Пивом или Пиво С Водкой)
2. Либо мы должно получить макет ДвоичныеДанные на стороне Клиента и там заполнить его, НО COM объект поддерживается только браузером IE и то с танцами с настройками ActiveX, другие браузеры давно отказались от использования ActiveX