Как получить и открыть присоединенный файл из связанного документа в 1С?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

В процессе разработки внутренних систем документооборота и кадрового учета в среде 1С часто возникает задача интеграции различных объектов. Типичный сценарий: на форме внешней обработки пользователь выбирает сотрудника или кандидата, и по нажатию кнопки система должна автоматически найти связанный документ, извлечь прикрепленный к нему файл (например, должностную инструкцию или резюме) и открыть его во внешнем приложении (MS Word, Adobe Reader и т.д.).

В данной статье мы подробно разберем, как правильно связать справочники и документы через запросы, как извлечь бинарные данные файла (как из самой базы 1С, так и из внешней базы SQL через ADO) и как корректно передать этот файл с сервера на клиент для последующего запуска.

Архитектура связей между объектами метаданных

Прежде чем переходить к написанию кода, проанализируем структуру данных, с которой предстоит работать. Часто в конфигурациях прямая связь между справочником и файлом отсутствует. Вместо этого используется цепочка связанных объектов:

  1. Справочник Кандидаты: содержит основную информацию о человеке (ФИО, должность, подразделение).
  2. Связующий документ (например, Документ.Кандидат): содержит реквизит Актив (ссылка на элемент справочника Кандидаты) и реквизит СЭДДокумент (ссылка на документ системы электронного документооборота).
  3. Документ СЭДДокумент: является владельцем версий файлов.
  4. Документ или справочник СЭДФайл: хранит метаданные конкретной версии файла (имя, расширение, идентификатор) и ссылку на сам документ-владелец.

Рассмотрим по шагам, как реализовать выборку и открытие файла в рамках этой архитектуры.

Шаг 1. Поиск связанного документа через запрос

Первая сложность, с которой сталкиваются начинающие разработчики, — это передача параметров в запрос для поиска связующего документа. Если передать в параметр текстовое значение (ФИО в виде строки) вместо ссылки на элемент справочника, платформа выдаст ошибку несоответствия типов.

Выясним причину: реквизит Актив в документе имеет тип СправочникСсылка.Кандидаты, а реквизит на форме обработки может быть настроен как строка или как ссылка. Нам необходимо передавать именно объектную ссылку.

Посмотрим на пример правильной установки параметров запроса для обычных форм:


// Получаем ссылку на кандидата из реквизита формы
СсылкаКандидат = ЭлементыФормы.ФИО.Значение; 

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Кандидат.СЭДДокумент КАК СЭДДокумент
    |ИЗ
    |	Документ.Кандидат КАК Кандидат
    |ГДЕ
    |	Кандидат.Актив = &КандидатСсылка";

Запрос.УстановитьПараметр("КандидатСсылка", СсылкаКандидат);
РезультатЗапроса = Запрос.Выполнить();

Если вы работаете в управляемых формах, обращение к реквизиту формы будет выглядеть еще проще — напрямую через имя реквизита объекта обработки: Объект.ФИО.

Шаг 2. Получение списка файлов (версий)

После того как мы успешно нашли ссылку на СЭДДокумент, нам необходимо получить сведения о файлах, привязанных к нему. Как выяснилось в ходе обсуждения темы, в данной конфигурации файлы не хранятся в табличной части самого документа. Вместо этого они хранятся в подчиненных документах типа СЭДФайл.

Разберем, как получить список файлов, используя вложенный запрос. Мы передаем найденный на предыдущем шаге документ в качестве параметра:


Если Не РезультатЗапроса.Пустой() Тогда
    ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
        
        // Создаем второй запрос для поиска файлов конкретного документа СЭД
        ЗапросФайлов = Новый Запрос;
        ЗапросФайлов.Текст = 
            "ВЫБРАТЬ
            |	СЭДФайл.Ссылка КАК Ссылка,
            |	СЭДФайл.Наименование КАК Наименование,
            |	СЭДФайл.РасширениеФайла КАК РасширениеФайла,
            |	СЭДФайл.ИдентификаторФайла КАК ИдентификаторФайла,
            |	СЭДФайл.ПараметрыПодключенияБазыДанных КАК ПараметрыПодключения
            |ИЗ
            |	Документ.СЭДФайл КАК СЭДФайл
            |ГДЕ
            |	СЭДФайл.Документ = &ДокументСсылка
            |УПОРЯДОЧИТЬ ПО
            |	СЭДФайл.Дата Изменение УБЫВ"; // Берем последнюю версию файла
            
        ЗапросФайлов.УстановитьПараметр("ДокументСсылка", ВыборкаДетальныеЗаписи.СЭДДокумент);
        ВыборкаФайлов = ЗапросФайлов.Выполнить().Выбрать();
        
        Если ВыборкаФайлов.Следующий() Тогда
            // Файл найден, переходим к его извлечению
            ИмяФайла = ВыборкаФайлов.Наименование;
            Расширение = ВыборкаФайлов.РасширениеФайла;
            Идентификатор = ВыборкаФайлов.ИдентификаторФайла;
            СтрокаПодключения = ВыборкаФайлов.ПараметрыПодключения;
            СсылкаНаСЭДФайл = ВыборкаФайлов.Ссылка;
        КонецЕсли;
        
    КонецЦикла;
КонецЕсли;

Шаг 3. Извлечение двоичных данных файла

В зависимости от архитектуры вашей информационной системы, двоичные данные файлов могут храниться в двух местах: во внешней СУБД (MS SQL Server / PostgreSQL) или непосредственно внутри базы данных 1С (в реквизите с типом ХранилищеЗначения). Рассмотрим оба варианта детально.

Вариант А. Получение данных из внешней SQL-базы через ADO (COM-объект)

Если в реквизитах файла хранятся параметры подключения к внешней СУБД и уникальный 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С, процедура извлечения значительно упрощается, так как платформа предоставляет штатный объект ХранилищеЗначения:


Функция ПолучитьДвоичныеДанныеИзХранилища(СсылкаНаСЭДФайл) Экспорт
    
    ОбъектФайл = СсылкаНаСЭДФайл.ПолучитьОбъект();
    
    // Предположим, реквизит называется ХранилищеФайла
    Если ТипЗнч(ОбъектФайл.ХранилищеФайла) = Тип("ХранилищеЗначения") Тогда
        ДвоичныеДанные = ОбъектФайл.ХранилищеФайла.Получить();
        Возврат ДвоичныеДанные; // Возвращает объект типа ДвоичныеДанные
    КонецЕсли;
    
    Возврат Неопределено;
    
КонецФункции

Шаг 4. Передача файла на клиент и запуск приложения

Проанализируем важный архитектурный аспект клиент-серверной работы 1С. Получение данных из SQL или ХранилищаЗначения всегда происходит на стороне сервера. Однако метод ЗапуститьПриложение() может быть выполнен только на стороне клиента, так как у серверного процесса нет графического интерфейса и доступа к локальным программам пользователя.

Для реализации этой цепочки мы должны поместить файл во временное хранилище на сервере, передать адрес клиенту, выгрузить файл на локальный диск пользователя и запустить его. Обязательно нужно передавать правильное расширение файла (например, .docx, .pdf), чтобы операционная система Windows знала, какой программой его открывать.

Разберем полный рабочий код взаимодействия клиента и сервера:


// Модуль управляемой формы или формы обработки

&НаСервере
Функция ПодготовитьФайлНаСервере(СсылкаНаСЭДФайл, СтрокаПодключения, ИдентификаторФайла)
    
    // Получаем двоичные данные одним из методов, описанных в Шаге 3
    Если ЗначениеЗаполнено(СтрокаПодключения) Тогда
        ДвоичныеДанные = ПолучитьДвоичныеДанныеИзSQL(СтрокаПодключения, ИдентификаторФайла);
    Иначе
        ДвоичныеДанные = ПолучитьДвоичныеДанныеИзХранилища(СсылкаНаСЭДФайл);
    EndIf;
    
    Если ДвоичныеДанные <> Неопределено Тогда
        // Помещаем во временное хранилище и возвращаем адрес клиенту
        Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, Новый УникальныйИдентификатор);
    КонецЕсли;
    
    Возврат "";
    
КонецФункции

&НаКлиенте
Процедура ОткрытьВложенныйФайл(СсылкаНаСЭДФайл, ИмяФайла, Расширение, СтрокаПодключения, ИдентификаторФайла)
    
    // Шаг 1. Получаем адрес файла во временном хранилище
    АдресХранилища = ПодготовитьФайлНаСервере(СсылкаНаСЭДФайл, СтрокаПодключения, ИдентификаторФайла);
    
    Если ПустаяСтрока(АдресХранилища) Тогда
        ПоказатьПредупреждение(, "Не удалось получить данные файла с сервера.");
        Возврат;
    КонецЕсли;
    
    // Шаг 2. Создаем путь к локальному временному файлу с правильным расширением
    // Важно: передаем расширение без точки (например, "docx"), платформа сама подставит точку
    ЛокальныйПуть = ПолучитьИмяВременногоФайла(Расширение);
    
    // Шаг 3. Получаем двоичные данные из хранилища на клиенте
    ДвоичныеДанныеКл = ПолучитьИзВременногоХранилища(АдресХранилища);
    
    // Шаг 4. Записываем файл на диск пользователя
    ДвоичныеДанныеКл.Записать(ЛокальныйПуть);
    
    // Шаг 5. Запускаем ассоциированное приложение ОС
    Попытка
        ЗапуститьПриложение(ЛокальныйПуть);
    Исключение
        ПоказатьПредупреждение(, "Не удалось запустить приложение для файла: " + ЛокальныйПуть + Символы.ПС + ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

Альтернатива: использование механизмов БСП

Если ваша конфигурация разработана на основе современных типовых решений фирмы 1С (УТ, ERP, КА, ЗУП, Документооборот), то в ней гарантированно присутствует **Библиотека стандартных подсистем (БСП)**. В этом случае ручное создание временных файлов и написание SQL-запросов к файлам не требуется.

В БСП встроена готовая подсистема **«Работа с файлами»**, которая берет на себя все низкоуровневые операции: кэширование файлов на клиенте, проверку прав доступа, открытие файлов различных версий и интеграцию со сторонним софтом. Для открытия файла достаточно использовать одну строчку кода на клиенте:


// Пример открытия присоединенного файла средствами БСП на клиенте
ПараметрыОткрытия = Новый Структура("ИдентификаторФормы", УникальныйИдентификатор);
РаботаСФайламиКлиент.ОткрытьФайл(СсылкаНаСЭДФайл, ПараметрыОткрытия);

Этот метод самостоятельно определит, где физически хранится файл (в томах на диске сервера или в СУБД), перенесет его во временный локальный каталог пользователя и выполнит безопасный запуск. Использование БСП — наиболее надежный и кроссплатформенный вариант, стабильно работающий как в тонком клиенте Windows/Linux, так и в веб-клиенте через браузер.

← На главную