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

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

При автоматизации бизнес-процессов в 1С часто возникает задача упрощения работы пользователя с прикрепленными файлами. Типичный сценарий: к документу Заказ поставщику менеджер прикрепляет спецификацию или счет в формате PDF. Затем на основании этого заказа бухгалтер создает документ Заявка на расходование денежных средств. Стандартно пользователю приходится заново скачивать файл из одного документа и вручную прикреплять его к другому.

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

Главная архитектурная сложность: Жизненный цикл документа

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

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

Правильная стратегия реализации задачи выглядит следующим образом:

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

Решение для современных конфигураций на базе БСП (ERP, КА 2, УТ 11, БП 3)

В современных прикладных решениях Библиотека Стандартных Подсистем (БСП) использует децентрализованную модель хранения данных. Для каждого типа документов создается свой индивидуальный справочник присоединенных файлов (например, ЗаказПоставщикуПрисоединенныеФайлы и ЗаявкаНаРасходованиеДенежныхСредствПрисоединенныеФайлы).

Типовой метод БСП РаботаСФайлами.СкопироватьПрисоединенныеФайлы(Источник, Получатель) предназначен для копирования файлов между объектами одного и того же типа. При копировании файлов между разными типами документов этот метод вызовет ошибку. Нам потребуется написать собственный алгоритм переноса.

Рассмотрим пошагово, как реализовать этот алгоритм:

  1. Выполнить запрос к справочнику присоединенных файлов документа-источника для получения списка файлов.
  2. В цикле получить двоичные данные каждого файла.
  3. Записать эти данные во временное хранилище.
  4. Создать и привязать новый файл к документу-приемнику с помощью программного интерфейса БСП.

Ниже представлен пример процедуры, которую можно разместить в модуле объекта документа-приемника (например, ЗаявкаНаРасходованиеДенежныхСредств) или в общем модуле:


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

Эту процедуру мы рекомендуем вызывать в модуле объекта документа-приемника внутри предопределенного обработчика ПриЗаписи, предварительно убедившись, что документ проводится или записывается впервые, и у него заполнен реквизит-основание.


Решение для старых конфигураций (УПП 1.3, КА 1.1, УТ 10.3)

В устаревших конфигурациях, работающих на обычных формах, использовался централизованный механизм хранения информации. Все файлы, изображения и дополнительные документы хранятся в одном общем справочнике — Справочник.ХранилищеДополнительнойИнформации. Связь с объектами базы данных осуществляется с помощью реквизита Объект, имеющего составной тип данных.

Такой подход значительно упрощает процедуру переноса файлов, так как нам не нужно работать с двоичными данными напрямую. Достаточно скопировать элемент справочника и изменить значение реквизита Объект.

Рассмотрим пример реализации:


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

Как правильно интегрировать решение в конфигурацию

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

Проанализируем пример интеграции через расширение для современной БСП-конфигурации:

  1. Создаем новое расширение и добавляем в него документ ЗаявкаНаРасходованиеДенежныхСредств.
  2. В модуле объекта документа в расширении создаем обработчик события ПриЗаписи с аннотацией &После.
  3. Добавляем проверку, чтобы копирование происходило только при первоначальной записи нового документа.

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

Важное примечание: Проверка ЭтоНовый() внутри обработчика ПриЗаписи может не сработать, так как в этот момент ссылка уже записана в базу данных. Правильнее будет определять статус «нового» документа в обработчике ПередЗаписью и сохранять признак в дополнительное свойство объекта:


&Перед("ПередЗаписью")
Процедура Расш1_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Если ЭтоНовый() Тогда
        ДополнительныеСвойства.Вставить("ЭтоНовыйДокумент", Истина);
    КонецЕсли;
    
КонецПроцедуры

&После("ПриЗаписи")
Процедура Расш1_ПриЗаписи(Отказ, Замещение)
    
    Если ДополнительныеСвойства.Свойство("ЭтоНовыйДокумент") Тогда
        Если ЗначениеЗаполнено(ЭтотОбъект.ДокументОснование) Тогда
            КопироватьФайлыИзИсточника(ЭтотОбъект.ДокументОснование, ЭтотОбъект.Ссылка);
        КонецЕсли;
    КонецЕсли;
    
КонецПроцедуры

Используя данный подход, мы получаем чистое решение, которое легко обновляется и полностью автоматизирует рутинную работу пользователей по ручному переносу документов и файлов между связанными объектами системы 1С.

← На главную