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

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

При разработке расширений или модификации типовых конфигураций на платформе 1С:Предприятие 8 часто возникает задача автоматического переноса вложенных файлов. Например, когда на основании документа Приобретение товаров и услуг создается Заявка на расходование денежных средств, логично ожидать, что все счета, акты и другие сканы документов «перекочуют» в новый объект. Однако обычное копирование записей в справочнике присоединенных файлов часто приводит к ошибке: «двоичные данные файла были удалены».

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

Почему простое копирование справочника не работает

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


НоваяСтрокаСправочника = Справочники.бит_кзн_ЗаявкаНаРасходованиеСредствПрисоединенныеФайлы.СоздатьЭлемент();
ЗаполнитьЗначенияСвойств(НоваяСтрокаСправочника, СтрокаТЗ); // СтрокаТЗ - данные исходного файла
НоваяСтрокаСправочника.ВладелецФайла = ДокументПланирования;
НоваяСтрокаСправочника.Записать();

На первый взгляд всё верно: мы создали элемент справочника, указали нового владельца и сохранили объект. Однако при попытке открыть такой файл пользователь увидит сообщение об ошибке. Причина кроется в том, что в современных конфигурациях (ERP, КА, УТ 11, БП 3.0) справочник присоединенных файлов — это лишь «верхушка айсберга» или метаданные. Сами двоичные данные (тело файла) хранятся отдельно.

Архитектура хранения файлов в БСП

Чтобы понять, как правильно копировать файлы, проанализируем структуру их хранения. БСП использует многоуровневую схему:

  1. Справочник-карточка файла (например, ДоговорыКонтрагентовПрисоединенныеФайлы). Здесь хранится наименование файла, ссылка на владельца (документ или справочник), информация о создании и флаг наличия данных.
  2. Справочник версий. Система поддерживает версионность. Даже если файл один, у него есть «текущая версия». Ссылка на неё хранится в карточке файла.
  3. Регистр сведений ДвоичныеДанныеФайлов. Если в настройках системы выбрано хранение в информационной базе, то именно в этом регистре, в ресурсе с типом ХранилищеЗначений, лежат байты вашего документа.
  4. Тома на диске. Если файлы хранятся вне базы, то в справочнике версий указывается путь к файлу на сервере и идентификатор тома.

Проанализируем ошибку автора: при копировании только элемента справочника (шаг 1) система создает запись-пустышку. Она не знает, где искать тело файла, так как записи в регистре ДвоичныеДанныеФайлов или соответствующей версии не существует.

Решение 1: Прямая запись в регистр двоичных данных

Если нам необходимо быстро решить проблему «здесь и сейчас» и мы точно знаем, что файлы хранятся в базе, а не на томах, можно дополнить код прямой записью в регистр. После того как мы записали элемент справочника присоединенных файлов, нам нужно найти исходные данные и перенести их.

Разберем алгоритм действий:

  1. Получаем двоичные данные из исходного файла (из регистра).
  2. Записываем их в регистр для нового файла.

Пример реализации:


// Создаем карточку нового файла
НовыйФайл = Справочники[ИмяСправочникаПрисоединенныхФайлов].СоздатьЭлемент();
ЗаполнитьЗначенияСвойств(НовыйФайл, ДанныеИсточника);
НовыйФайл.ВладелецФайла = НовыйВладелец;
НовыйФайл.Записать();

// Переносим двоичные данные в регистр
МенеджерЗаписи = РегистрыСведений.ДвоичныеДанныеФайлов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Файл = НовыйФайл.Ссылка; // Или ссылка на версию, зависит от версии БСП

// Получаем данные старого файла
ДанныеСтарогоФайла = РегистрыСведений.ДвоичныеДанныеФайлов.СоздатьМенеджерЗаписи();
ДанныеСтарогоФайла.Файл = СтараяСсылкаНаФайл;
ДанныеСтарогоФайла.Прочитать();

Если ДанныеСтарогоФайла.Выбран() Тогда
    МенеджерЗаписи.ДвоичныеДанные = ДанныеСтарогоФайла.ДвоичныеДанные;
    МенеджерЗаписи.Записать();
КонецЕсли;

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

Решение 2: Использование программного интерфейса БСП (Рекомендуется)

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

Проанализируем метод СкопироватьФайл. Этот метод берет на себя всю рутину по созданию записей в справочниках версий, копированию данных в регистры или на дисковые тома, а также корректно прописывает GUID.

Рассмотрим пример использования:


// ИсходныйФайлСсылка - это ссылка на элемент справочника присоединенных файлов источника
// НовыйВладелец - ссылка на документ-приемник

ПараметрыКопирования = РаботаСФайлами.ПараметрыКопированияФайла();
ПараметрыКопирования.ВладелецФайла = НовыйВладелец;

НовыйФайлСсылка = РаботаСФайлами.СкопироватьФайл(ИсходныйФайлСсылка, ПараметрыКопирования);

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

Решение 3: Динамическое отображение файлов без копирования

Иногда копирование данных — это путь к неоправданному росту объема базы. Если вы копируете файлы из Заказа в Реализацию, а затем в Счет-фактуру, один и тот же тяжелый PDF-документ может занять в три раза больше места.

Выясним, можно ли избежать дублирования. Вместо физического копирования можно подменить список отображаемых файлов в форме документа-приемника. Для этого в динамическом списке, который выводит присоединенные файлы, можно изменить запрос так, чтобы он выбирал файлы не только текущего документа, но и документа-основания.

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

  1. В форме документа в списке файлов находим ДинамическийСписок.
  2. Меняем его текст запроса, добавляя условие ГДЕ ВладелецФайла В (&МассивВладельцев).
  3. В МассивВладельцев передаем ссылку на текущий документ и ссылку на документ-основание.

Этот способ экономит место и позволяет видеть историю файлов «сквозь» документы.

Особые моменты: Права доступа и уникальные идентификаторы

При ручном копировании (как в Решении 1) часто забывают про права доступа. В БСП за права на просмотр файлов отвечают отдельные механизмы (регистры прав). Если вы просто добавили запись в базу «в обход» стандартных методов, пользователь может получить ошибку доступа, даже если двоичные данные на месте.

Основные выводы:

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

← На главную