Присоединённые файлы из кода: от диска и печатной формы до удаления
Содержание
Чтобы программно добавить файл
к документу 1С, не пишите элемент справочника присоединённых файлов «руками» через Справочники.….СоздатьЭлемент(). В типовых конфигурациях на БСП 3.x (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) правильный путь один: поместить двоичные данные во временное хранилище и вызвать на сервере РаботаСФайлами.ДобавитьФайл(). Эта функция сама создаст карточку, сохранит данные туда, где их хранит база (в ИБ или в томах на диске), и вернёт ссылку на новый файл.
Ручная запись элемента — частая ошибка. Карточка появляется, но двоичных данных у неё нет: файл в списке виден, а открыть его нельзя. Источник данных при этом не важен: файл с диска пользователя, файл на сервере, печатная форма в PDF или строка Base64 из обмена — всё сводится к адресу во временном хранилище.
Где 1С хранит присоединённые файлы документа
Прежде чем писать код, полезно понимать, что именно создаёт ДобавитьФайл. В подсистеме «Работа с файлами» БСП файл состоит из двух частей.
- 🗂 Карточка — элемент справочника. Для большинства документов это отдельный справочник с именем
<ИмяВладельца>ПрисоединенныеФайлы, напримерРеализацияТоваровУслугПрисоединенныеФайлы. Ссылка на документ лежит в реквизитеВладелецФайла. - 💾 Двоичные данные — сами байты. Где они лежат, зависит от настройки базы: в служебном регистре сведений внутри ИБ или в файлах на диске, в томах хранения (справочник
ТомаХраненияФайлов). - 🏷 Тип хранения записан в карточке, в реквизите
ТипХраненияФайла(значения перечисленияТипыХраненияФайлов:ВИнформационнойБазеиВТомахНаДиске).
Прямо читать регистр или искать файл в томе не нужно. Подсистема прячет эту разницу за общими функциями, и код работает одинаково при любом варианте хранения.
Как узнать, к чему можно прикреплять файлы
Владелец должен входить в определяемый тип ВладелецПрисоединенныхФайлов. Откройте его в конфигураторе: Общие → Определяемые типы. Все типовые документы бухгалтерии там уже есть.
Для своего документа в расширении или доработанной конфигурации порядок такой:
- Добавьте документ в состав определяемого типа
ВладелецПрисоединенныхФайлов. - Создайте справочник
<ИмяДокумента>ПрисоединенныеФайлыкопированием из любого типового справочника…ПрисоединенныеФайлы. Так сохранятся все реквизиты и формы. - В реквизите
ВладелецФайланового справочника оставьте тип — ссылку на ваш документ. - Выведите на форму документа команду работы с файлами, как в типовых документах.
Если у документа нет своего справочника …ПрисоединенныеФайлы, БСП может хранить его файлы в общем справочнике Файлы. Тогда владелец ищется по определяемому типу ВладелецФайлов. Функция ДобавитьФайл выберет справочник сама, ваш код не меняется.
Параметры функции ДобавитьФайл
В БСП 3.x вызов выглядит так:
НовыйФайл = РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, АдресВоВременномХранилище, , Описание);
Первый параметр собирают функцией РаботаСФайлами.ПараметрыДобавленияФайла(). Она возвращает структуру, в которой нужно заполнить поля:
| Поле | Что указать |
|---|---|
ВладелецФайлов | Ссылка на документ |
ИмяБезРасширения | Имя файла без точки и расширения |
РасширениеБезТочки | pdf, xlsx, jpg — без точки |
ВремяИзмененияУниверсальное | Дата изменения в UTC |
Автор | Пользователь, от чьего имени добавлен файл |
Служебный | Истина, если файл не должен показываться в списке пользователю |
Третий параметр — адрес временного хранилища с извлечённым текстом для полнотекстового поиска. Обычно его пропускают. Четвёртый — описание файла, оно видно в карточке.
В старых конфигурациях на БСП 2.x те же действия делал модуль ПрисоединенныеФайлы с длинным списком позиционных параметров. Если в вашей базе нет функции ПараметрыДобавленияФайла, откройте общий модуль ПрисоединенныеФайлы и посмотрите сигнатуру ДобавитьФайл в нём. Не переносите код между версиями БСП без проверки.
Добавление файла с компьютера пользователя
Файл с диска пользователя сначала нужно передать на сервер. Синхронные методы в тонком и веб-клиенте использовать не стоит, поэтому берём НачатьПомещениеФайла. Он открывает диалог выбора, кладёт файл во временное хранилище и вызывает обработчик.
Код в модуле формы документа:
&НаКлиенте
Процедура ПрикрепитьФайл(Команда)
Если Объект.Ссылка.Пустая() Тогда
ПоказатьПредупреждение(, "Сначала запишите документ.");
Возврат;
КонецЕсли;
Оповещение = Новый ОписаниеОповещения("ПрикрепитьФайлЗавершение", ЭтотОбъект);
НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПрикрепитьФайлЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Не Результат Тогда
Возврат; // пользователь закрыл диалог
КонецЕсли;
НовыйФайл = ДобавитьФайлНаСервере(Адрес, ВыбранноеИмяФайла);
ПоказатьОповещениеПользователя("Файл прикреплён", ПолучитьНавигационнуюСсылку(НовыйФайл));
КонецПроцедуры
&НаСервере
Функция ДобавитьФайлНаСервере(Адрес, ПолноеИмяФайла)
// На клиенте путь может быть с другим разделителем — берём имя после последнего слеша
ИмяФайла = СтрЗаменить(ПолноеИмяФайла, "/", "\");
Части = СтрРазделить(ИмяФайла, "\");
ИмяФайла = Части[Части.ВГраница()];
ПозицияТочки = СтрНайти(ИмяФайла, ".", НаправлениеПоиска.СКонца);
Если ПозицияТочки > 0 Тогда
ИмяБезРасширения = Лев(ИмяФайла, ПозицияТочки - 1);
Расширение = Сред(ИмяФайла, ПозицияТочки + 1);
Иначе
ИмяБезРасширения = ИмяФайла;
Расширение = "";
КонецЕсли;
ПараметрыФайла = РаботаСФайлами.ПараметрыДобавленияФайла();
ПараметрыФайла.ВладелецФайлов = Объект.Ссылка;
ПараметрыФайла.ИмяБезРасширения = ИмяБезРасширения;
ПараметрыФайла.РасширениеБезТочки = НРег(Расширение);
ПараметрыФайла.ВремяИзмененияУниверсальное = ТекущаяУниверсальнаяДата();
ПараметрыФайла.Автор = Пользователи.ТекущийПользователь();
Возврат РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес);
КонецФункции
Три момента, на которых спотыкаются:
- Документ должен быть записан. У нового документа пустая ссылка, и прикрепить файл не к чему.
УникальныйИдентификаторформы во втором вызове привязывает временное хранилище к форме. Данные живут, пока открыта форма, и не висят в памяти сервера после её закрытия.- Имя файла с клиента приходит с полным путём. Файл с именем
Акт.сверки.pdfсодержит две точки, поэтому расширение ищем с конца строки.
Если нужна загрузка сразу нескольких файлов с типовым интерфейсом (диалог, проверка размера, прогресс), посмотрите общий модуль РаботаСФайламиКлиент. Там есть готовые процедуры интерактивного добавления, они используют те же серверные функции.
Прикрепление файла, который уже лежит на сервере
Обмены, обработки загрузки, регламентные задания — во всех этих случаях файл уже на сервере или в каталоге, доступном серверу 1С. Клиента нет, диалога нет.
Функция ПрикрепитьФайлСДиска(ДокументСсылка, ПолноеИмяФайла) Экспорт
Файл = Новый Файл(ПолноеИмяФайла);
Если Не Файл.Существует() Тогда
ВызватьИсключение СтрШаблон("Файл не найден: %1", ПолноеИмяФайла);
КонецЕсли;
ДвоичныеДанные = Новый ДвоичныеДанные(ПолноеИмяФайла);
Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
ПараметрыФайла = РаботаСФайлами.ПараметрыДобавленияФайла();
ПараметрыФайла.ВладелецФайлов = ДокументСсылка;
ПараметрыФайла.ИмяБезРасширения = Файл.ИмяБезРасширения;
ПараметрыФайла.РасширениеБезТочки = НРег(Сред(Файл.Расширение, 2)); // у Файл.Расширение есть точка
ПараметрыФайла.ВремяИзмененияУниверсальное = Файл.ПолучитьУниверсальноеВремяИзменения();
НовыйФайл = РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес);
УдалитьИзВременногоХранилища(Адрес);
Возврат НовыйФайл;
КонецФункции
Путь в ПолноеИмяФайла — это путь на сервере 1С, а не на компьютере пользователя. В клиент-серверной базе код выполняется от имени пользователя, под которым запущена служба сервера. Если каталог сетевой, у этой учётной записи должны быть права на чтение.
В регламентном задании Пользователи.ТекущийПользователь() вернёт пользователя, от имени которого запущено задание. Если автором должен быть конкретный сотрудник, передайте его явно.
Печатная форма в PDF как вложение
Типовая задача: при проведении или по кнопке сформировать счёт или акт в PDF и прикрепить к документу, чтобы потом отправить по почте или в ЭДО. Табличный документ сохраняют во временный файл, читают байты и передают в ДобавитьФайл.
Функция ПрикрепитьПечатнуюФормуPDF(ДокументСсылка, ТабличныйДокумент, ИмяФайла) Экспорт
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");
ТабличныйДокумент.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.PDF);
ДвоичныеДанные = Новый ДвоичныеДанные(ИмяВременногоФайла);
УдалитьФайлы(ИмяВременногоФайла);
Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
ПараметрыФайла = РаботаСФайлами.ПараметрыДобавленияФайла();
ПараметрыФайла.ВладелецФайлов = ДокументСсылка;
ПараметрыФайла.ИмяБезРасширения = ИмяФайла;
ПараметрыФайла.РасширениеБезТочки = "pdf";
ПараметрыФайла.ВремяИзмененияУниверсальное = ТекущаяУниверсальнаяДата();
НовыйФайл = РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес, , "Сформировано автоматически");
УдалитьИзВременногоХранилища(Адрес);
Возврат НовыйФайл;
КонецФункции
Сам ТабличныйДокумент берите из штатной процедуры печати документа. В типовых конфигурациях печать идёт через подсистему «Печать» БСП (общий модуль УправлениеПечатью) и модуль менеджера документа. Сигнатуры там различаются от конфигурации к конфигурации, поэтому найдите в модуле менеджера процедуру печати нужной формы и вызовите её. Ваш собственный макет тоже подойдёт.
Для имени файла соберите строку из номера и даты:
ИмяФайла = СтрШаблон("Счет %1 от %2", ДокументСсылка.Номер, Формат(ДокументСсылка.Дата, "ДФ=dd.MM.yyyy"));
Не вызывайте добавление файла внутри ПриЗаписи или ОбработкаПроведения без необходимости. Если база хранит файлы в томах, байты пишутся на диск сразу. При откате транзакции карточки не будет, а файл в томе останется мусором. Надёжнее прикреплять печатную форму после успешной записи — например, в ПослеЗаписиНаСервере формы или отдельным вызовом.
Потоки и двоичные данные без временных файлов
Временный файл на диске — лишняя операция и лишняя точка отказа. Если данные уже в памяти, работайте с ними напрямую. Функции ДобавитьФайл нужен только адрес хранилища с объектом ДвоичныеДанные.
| Источник | Как получить ДвоичныеДанные |
|---|---|
| Строка Base64 (из JSON, XML, веб-сервиса) | Base64Значение(Строка) |
| Ответ HTTP-сервиса | HTTPОтвет.ПолучитьТелоКакДвоичныеДанные() |
| Текст, сформированный в коде | ПолучитьДвоичныеДанныеИзСтроки(Текст, КодировкаТекста.UTF8) |
| Данные, собранные по частям | ПотокВПамяти + ЗаписьДанных |
Реквизит типа ХранилищеЗначения | Хранилище.Получить() — если внутри лежат двоичные данные |
Пример: формируем CSV-реестр по строкам документа и прикрепляем его, ни разу не обращаясь к диску.
Функция ПрикрепитьРеестрCSV(ДокументСсылка) Экспорт
Поток = Новый ПотокВПамяти;
Запись = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Номенклатура;Количество;Сумма");
Для Каждого Строка Из ДокументСсылка.Товары Цикл
Запись.ЗаписатьСтроку(СтрШаблон("%1;%2;%3",
Строка.Номенклатура,
Формат(Строка.Количество, "ЧГ=0"),
Формат(Строка.Сумма, "ЧДЦ=2; ЧРД=.; ЧГ=0")));
КонецЦикла;
Запись.Закрыть();
ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
ПараметрыФайла = РаботаСФайлами.ПараметрыДобавленияФайла();
ПараметрыФайла.ВладелецФайлов = ДокументСсылка;
ПараметрыФайла.ИмяБезРасширения = "Реестр товаров";
ПараметрыФайла.РасширениеБезТочки = "csv";
ПараметрыФайла.ВремяИзмененияУниверсальное = ТекущаяУниверсальнаяДата();
Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
НовыйФайл = РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес);
УдалитьИзВременногоХранилища(Адрес);
Возврат НовыйФайл;
КонецФункции
Имя табличной части Товары — пример: в конкретном документе она может называться иначе. Формат(…, "ЧГ=0") убирает пробелы-разделители групп, иначе число 1 250 попадёт в CSV с неразрывным пробелом и сломает импорт в Excel.
ЗаписьДанных.Закрыть() сбрасывает буфер и закрывает сам объект записи, но не поток. Поэтому после неё безопасно вызвать Поток.ЗакрытьИПолучитьДвоичныеДанные().
Размер, расширения и ошибки при добавлении
Интерактивная загрузка через типовую форму сверяется с настройками подсистемы: максимальный размер файла и список запрещённых расширений. Их задают в разделе Администрирование, в настройках работы с файлами. Программный код лучше проверять самому, до вызова ДобавитьФайл, и не рассчитывать, что подсистема отсеет лишнее за вас.
МаксРазмерМБ = 50;
Если ДвоичныеДанные.Размер() > МаксРазмерМБ * 1024 * 1024 Тогда
ВызватьИсключение СтрШаблон("Файл больше %1 МБ, прикреплять не будем.", МаксРазмерМБ);
КонецЕсли;
Здесь 50 × 1024 × 1024 = 52 428 800 байт. ДвоичныеДанные.Размер() возвращает размер именно в байтах.
Вызов ДобавитьФайл в обмене или задании оборачивайте в попытку и пишите ошибку в журнал регистрации. Иначе один битый файл остановит загрузку всего пакета.
Попытка
ПрикрепитьФайлСДиска(ДокументСсылка, ПолноеИмяФайла);
Исключение
ЗаписьЖурналаРегистрации("Загрузка вложений", УровеньЖурналаРегистрации.Ошибка,
, ДокументСсылка, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Чтение, замена и удаление вложений
Найти файлы документа проще всего запросом к справочнику присоединённых файлов:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Файлы.Ссылка КАК Ссылка,
| Файлы.Наименование КАК Наименование,
| Файлы.Расширение КАК Расширение,
| Файлы.Размер КАК Размер
|ИЗ
| Справочник.РеализацияТоваровУслугПрисоединенныеФайлы КАК Файлы
|ГДЕ
| Файлы.ВладелецФайла = &Владелец
| И НЕ Файлы.ПометкаУдаления";
Запрос.УстановитьПараметр("Владелец", ДокументСсылка);
Выборка = Запрос.Выполнить().Выбрать();
Байты файла получают функцией РаботаСФайлами.ДвоичныеДанныеФайла(Ссылка). Она сама разберётся, лежат данные в ИБ или в томе. Для передачи пользователю на клиент есть РаботаСФайлами.ДанныеФайла(Ссылка) — она возвращает структуру с навигационной ссылкой на двоичные данные и описанием файла.
Замена содержимого
Чтобы обновить уже прикреплённый файл (например, перегенерированный акт), не обязательно удалять старый. РаботаСФайлами.ОбновитьФайл() записывает новые данные в существующую карточку, и ссылки на неё в других объектах не теряются. Перед вызовом проверьте сигнатуру в вашей версии БСП: состав структуры с информацией о файле менялся между релизами.
Удаление
Удаляйте вложение так же, как любой объект справочника: помечайте на удаление.
ФайлОбъект = ФайлСсылка.ПолучитьОбъект();
ФайлОбъект.УстановитьПометкуУдаления(Истина);
Физически запись и данные исчезнут при удалении помеченных объектов. Если файл лежал в томе, при удалении карточки подсистема удаляет и файл на диске.
Не удаляйте файлы из каталога тома вручную через проводник. Карточки останутся, а при открытии пользователи получат ошибку открытия файла. Восстановить их можно будет только из резервной копии каталога.
| Действие | Как делать | Чего избегать |
|---|---|---|
| Скрыть файл из списка | Пометка удаления | Удаления записи без пометки в рабочее время |
| Удалить навсегда | Удаление помеченных объектов | Ручной очистки каталога тома |
| Заменить содержимое | ОбновитьФайл | Удаления и повторного добавления, если на файл есть ссылки |