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