В процессе разработки внутренних систем документооборота и кадрового учета в среде 1С часто возникает задача интеграции различных объектов. Типичный сценарий: на форме внешней обработки пользователь выбирает сотрудника или кандидата, и по нажатию кнопки система должна автоматически найти связанный документ, извлечь прикрепленный к нему файл (например, должностную инструкцию или резюме) и открыть его во внешнем приложении (MS Word, Adobe Reader и т.д.).
В данной статье мы подробно разберем, как правильно связать справочники и документы через запросы, как извлечь бинарные данные файла (как из самой базы 1С, так и из внешней базы SQL через ADO) и как корректно передать этот файл с сервера на клиент для последующего запуска.
Прежде чем переходить к написанию кода, проанализируем структуру данных, с которой предстоит работать. Часто в конфигурациях прямая связь между справочником и файлом отсутствует. Вместо этого используется цепочка связанных объектов:
Актив (ссылка на элемент справочника Кандидаты) и реквизит СЭДДокумент (ссылка на документ системы электронного документооборота).Рассмотрим по шагам, как реализовать выборку и открытие файла в рамках этой архитектуры.
Первая сложность, с которой сталкиваются начинающие разработчики, — это передача параметров в запрос для поиска связующего документа. Если передать в параметр текстовое значение (ФИО в виде строки) вместо ссылки на элемент справочника, платформа выдаст ошибку несоответствия типов.
Выясним причину: реквизит Актив в документе имеет тип СправочникСсылка.Кандидаты, а реквизит на форме обработки может быть настроен как строка или как ссылка. Нам необходимо передавать именно объектную ссылку.
Посмотрим на пример правильной установки параметров запроса для обычных форм:
// Получаем ссылку на кандидата из реквизита формы
СсылкаКандидат = ЭлементыФормы.ФИО.Значение;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Кандидат.СЭДДокумент КАК СЭДДокумент
|ИЗ
| Документ.Кандидат КАК Кандидат
|ГДЕ
| Кандидат.Актив = &КандидатСсылка";
Запрос.УстановитьПараметр("КандидатСсылка", СсылкаКандидат);
РезультатЗапроса = Запрос.Выполнить();
Если вы работаете в управляемых формах, обращение к реквизиту формы будет выглядеть еще проще — напрямую через имя реквизита объекта обработки: Объект.ФИО.
После того как мы успешно нашли ссылку на СЭДДокумент, нам необходимо получить сведения о файлах, привязанных к нему. Как выяснилось в ходе обсуждения темы, в данной конфигурации файлы не хранятся в табличной части самого документа. Вместо этого они хранятся в подчиненных документах типа СЭДФайл.
Разберем, как получить список файлов, используя вложенный запрос. Мы передаем найденный на предыдущем шаге документ в качестве параметра:
Если Не РезультатЗапроса.Пустой() Тогда
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Создаем второй запрос для поиска файлов конкретного документа СЭД
ЗапросФайлов = Новый Запрос;
ЗапросФайлов.Текст =
"ВЫБРАТЬ
| СЭДФайл.Ссылка КАК Ссылка,
| СЭДФайл.Наименование КАК Наименование,
| СЭДФайл.РасширениеФайла КАК РасширениеФайла,
| СЭДФайл.ИдентификаторФайла КАК ИдентификаторФайла,
| СЭДФайл.ПараметрыПодключенияБазыДанных КАК ПараметрыПодключения
|ИЗ
| Документ.СЭДФайл КАК СЭДФайл
|ГДЕ
| СЭДФайл.Документ = &ДокументСсылка
|УПОРЯДОЧИТЬ ПО
| СЭДФайл.Дата Изменение УБЫВ"; // Берем последнюю версию файла
ЗапросФайлов.УстановитьПараметр("ДокументСсылка", ВыборкаДетальныеЗаписи.СЭДДокумент);
ВыборкаФайлов = ЗапросФайлов.Выполнить().Выбрать();
Если ВыборкаФайлов.Следующий() Тогда
// Файл найден, переходим к его извлечению
ИмяФайла = ВыборкаФайлов.Наименование;
Расширение = ВыборкаФайлов.РасширениеФайла;
Идентификатор = ВыборкаФайлов.ИдентификаторФайла;
СтрокаПодключения = ВыборкаФайлов.ПараметрыПодключения;
СсылкаНаСЭДФайл = ВыборкаФайлов.Ссылка;
КонецЕсли;
КонецЦикла;
КонецЕсли;
В зависимости от архитектуры вашей информационной системы, двоичные данные файлов могут храниться в двух местах: во внешней СУБД (MS SQL Server / PostgreSQL) или непосредственно внутри базы данных 1С (в реквизите с типом ХранилищеЗначения). Рассмотрим оба варианта детально.
Если в реквизитах файла хранятся параметры подключения к внешней СУБД и уникальный GUID файла, мы можем выполнить прямое чтение BLOB-поля (двоичных данных) с помощью технологии ADO. Для этого на сервере 1С выполняется следующий алгоритм:
Функция ПолучитьДвоичныеДанныеИзSQL(СтрокаПодключения, ИдентификаторФайла) Экспорт
// Инициализируем COM-соединение с внешней базой данных
Connection = Новый COMОбъект("ADODB.Connection");
Попытка
Connection.Open(СтрокаПодключения);
Исключение
ЗаписьЖурналаРегистрации("Подключение к SQL", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
ВызватьИсключение "Не удалось подключиться к внешней базе данных SQL!";
КонецПопытки;
// Формируем SQL-запрос для получения бинарного потока
RecordSet = Новый COMОбъект("ADODB.Recordset");
ТекстЗапросаSQL = "SELECT [BinaryData] FROM [FileStorageTable] WHERE [FileID] = '" + ИдентификаторФайла + "'";
Попытка
RecordSet.Open(ТекстЗапросаSQL, Connection);
Исключение
Connection.Close();
ВызватьИсключение "Ошибка выполнения SQL-запроса при получении файла!";
КонецПопытки;
Если НЕ RecordSet.EOF Тогда
// Используем ADODB.Stream для сохранения бинарного потока в файл на сервере
BinaryStream = Новый COMОбъект("ADODB.Stream");
BinaryStream.Type = 1; // adTypeBinary (работаем с двоичными данными)
BinaryStream.Open();
BinaryStream.Write(RecordSet.Fields("BinaryData").Value);
// Создаем временный файл на сервере
ВременныйПуть = ПолучитьИмяВременногоФайла();
BinaryStream.SaveToFile(ВременныйПуть, 2); // adSaveCreateOverWrite
BinaryStream.Close();
// Читаем данные в объект "ДвоичныеДанные" 1С
ДвоичныеДанные = Новый ДвоичныеДанные(ВременныйПуть);
// Удаляем временный файл после прочтения
УдалитьФайлы(ВременныйПуть);
RecordSet.Close();
Connection.Close();
Возврат ДвоичныеДанные;
Иначе
RecordSet.Close();
Connection.Close();
Возврат Неопределено;
EndIf;
КонецФункции
Если файлы хранятся во внутренней базе данных 1С, процедура извлечения значительно упрощается, так как платформа предоставляет штатный объект ХранилищеЗначения:
Функция ПолучитьДвоичныеДанныеИзХранилища(СсылкаНаСЭДФайл) Экспорт
ОбъектФайл = СсылкаНаСЭДФайл.ПолучитьОбъект();
// Предположим, реквизит называется ХранилищеФайла
Если ТипЗнч(ОбъектФайл.ХранилищеФайла) = Тип("ХранилищеЗначения") Тогда
ДвоичныеДанные = ОбъектФайл.ХранилищеФайла.Получить();
Возврат ДвоичныеДанные; // Возвращает объект типа ДвоичныеДанные
КонецЕсли;
Возврат Неопределено;
КонецФункции
Проанализируем важный архитектурный аспект клиент-серверной работы 1С. Получение данных из SQL или ХранилищаЗначения всегда происходит на стороне сервера. Однако метод ЗапуститьПриложение() может быть выполнен только на стороне клиента, так как у серверного процесса нет графического интерфейса и доступа к локальным программам пользователя.
Для реализации этой цепочки мы должны поместить файл во временное хранилище на сервере, передать адрес клиенту, выгрузить файл на локальный диск пользователя и запустить его. Обязательно нужно передавать правильное расширение файла (например, .docx, .pdf), чтобы операционная система Windows знала, какой программой его открывать.
Разберем полный рабочий код взаимодействия клиента и сервера:
// Модуль управляемой формы или формы обработки
&НаСервере
Функция ПодготовитьФайлНаСервере(СсылкаНаСЭДФайл, СтрокаПодключения, ИдентификаторФайла)
// Получаем двоичные данные одним из методов, описанных в Шаге 3
Если ЗначениеЗаполнено(СтрокаПодключения) Тогда
ДвоичныеДанные = ПолучитьДвоичныеДанныеИзSQL(СтрокаПодключения, ИдентификаторФайла);
Иначе
ДвоичныеДанные = ПолучитьДвоичныеДанныеИзХранилища(СсылкаНаСЭДФайл);
EndIf;
Если ДвоичныеДанные <> Неопределено Тогда
// Помещаем во временное хранилище и возвращаем адрес клиенту
Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, Новый УникальныйИдентификатор);
КонецЕсли;
Возврат "";
КонецФункции
&НаКлиенте
Процедура ОткрытьВложенныйФайл(СсылкаНаСЭДФайл, ИмяФайла, Расширение, СтрокаПодключения, ИдентификаторФайла)
// Шаг 1. Получаем адрес файла во временном хранилище
АдресХранилища = ПодготовитьФайлНаСервере(СсылкаНаСЭДФайл, СтрокаПодключения, ИдентификаторФайла);
Если ПустаяСтрока(АдресХранилища) Тогда
ПоказатьПредупреждение(, "Не удалось получить данные файла с сервера.");
Возврат;
КонецЕсли;
// Шаг 2. Создаем путь к локальному временному файлу с правильным расширением
// Важно: передаем расширение без точки (например, "docx"), платформа сама подставит точку
ЛокальныйПуть = ПолучитьИмяВременногоФайла(Расширение);
// Шаг 3. Получаем двоичные данные из хранилища на клиенте
ДвоичныеДанныеКл = ПолучитьИзВременногоХранилища(АдресХранилища);
// Шаг 4. Записываем файл на диск пользователя
ДвоичныеДанныеКл.Записать(ЛокальныйПуть);
// Шаг 5. Запускаем ассоциированное приложение ОС
Попытка
ЗапуститьПриложение(ЛокальныйПуть);
Исключение
ПоказатьПредупреждение(, "Не удалось запустить приложение для файла: " + ЛокальныйПуть + Символы.ПС + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Если ваша конфигурация разработана на основе современных типовых решений фирмы 1С (УТ, ERP, КА, ЗУП, Документооборот), то в ней гарантированно присутствует **Библиотека стандартных подсистем (БСП)**. В этом случае ручное создание временных файлов и написание SQL-запросов к файлам не требуется.
В БСП встроена готовая подсистема **«Работа с файлами»**, которая берет на себя все низкоуровневые операции: кэширование файлов на клиенте, проверку прав доступа, открытие файлов различных версий и интеграцию со сторонним софтом. Для открытия файла достаточно использовать одну строчку кода на клиенте:
// Пример открытия присоединенного файла средствами БСП на клиенте
ПараметрыОткрытия = Новый Структура("ИдентификаторФормы", УникальныйИдентификатор);
РаботаСФайламиКлиент.ОткрытьФайл(СсылкаНаСЭДФайл, ПараметрыОткрытия);
Этот метод самостоятельно определит, где физически хранится файл (в томах на диске сервера или в СУБД), перенесет его во временный локальный каталог пользователя и выполнит безопасный запуск. Использование БСП — наиболее надежный и кроссплатформенный вариант, стабильно работающий как в тонком клиенте Windows/Linux, так и в веб-клиенте через браузер.