При автоматизации процессов в "1С:Документооборот 3.0" (ДО 3.0), учитывая все нюансы перехода с версии 2.1, мы часто сталкиваемся с задачей генерации печатных форм (например, объединяя файлы внутреннего документа в один PDF) и их автоматического прикрепления к карточке документа. Однако задача усложняется, когда этот процесс повторяется многократно: нам нужно не плодить дубликаты файлов, а корректно обновлять версии уже существующего файла. В этой статье мы подробно разберем, как правильно реализовать алгоритм "Создать новый или обновить существующий", избежать ошибок с потерей двоичных данных и корректно работать с методами подсистемы работы с файлами.
Рассмотрим ситуацию: у нас есть процесс, который генерирует PDF-файл на основе данных документа. Наша цель — сохранить этот файл в системе. Подобные задачи часто возникают, когда реализуется программный перенос присоединенных файлов в рамках интеграционных решений.
Логика работы должна быть следующей:
На практике при реализации этого алгоритма разработчики часто сталкиваются с ошибками вида:
Неверный тип данных для добавления на томЗначение не является значением объектного типа (Размер)Давайте разберемся, почему эти ошибки возникают и как написать код, который будет работать стабильно.
В исходной ситуации ошибка возникала в момент попытке добавить файл, причем только тогда, когда в код добавлялся блок поиска существующего файла. Без поиска (просто создание) все работало.
Причина кроется в механизме работы ВременноеХранилище. Когда мы помещаем данные во временное хранилище с помощью метода ПоместитьВоВременноеХранилище, мы получаем адрес. Если мы не передаем третьим параметром Уникальный Идентификатор формы (UUID), то данные во временном хранилище живут только в течение одного серверного вызова.
Рассмотрим ошибочный сценарий:
К моменту выполнения пункта 4 адрес временного хранилища, полученный в пункте 1, может быть уже невалиден или очищен платформой, так как произошел разрыв контекста. В результате в методы записи файла передается Неопределено вместо двоичных данных, что вызывает ошибку чтения свойства Размер().
Чтобы избежать потери данных и лишних вызовов между клиентом и сервером, мы должны инкапсулировать (объединить) всю логику работы с файлом в одну серверную процедуру. Мы передадим на сервер ссылку на документ-владелец и сами данные, а внутри процедуры решим, создавать файл или обновлять версию.
Давайте пошагово реализуем правильный алгоритм.
Сначала нам нужно получить сам файл. В ДО 3.0 часто используется механизм визуализации электронной подписи или просто формирование печатного образа. Получим двоичные данные PDF.
// Пример получения данных (может выполняться в отдельном вызове)
ПолучаемФайлСЭП = РаботаСФайламиВызовСервера.ПолучитьФайлВизуализации(ТекущиеДанные.Ссылка, УникальныйИдентификатор, Истина, Истина);
// Получаем имя временного файла
FilePDFDest = ПолучитьИмяВременногоФайла("pdf");
// Извлекаем двоичные данные из результата визуализации
ДвоичныеДанныеPDF = РаботаСФайламиВызовСервера.ПолучитьДвоичныеДанныеФайла(ПолучаемФайлСЭП);
Важный момент: Всегда проверяйте, что ДвоичныеДанныеPDF не равны Неопределено. Генерация визуализации может занять время, и файл может быть еще не готов. Если же вам требуется дополнительно модифицировать файл, например, выполнить установку штампа или изображения в PDF средствами платформы, делайте это до помещения данных в хранилище для финальной записи.
Теперь создадим процедуру, которая примет данные и выполнит всю работу. Это ключевое место решения. Мы не будем возвращать управление на клиент между поиском и записью.
&НаСервере
Процедура ОбновитьИлиСоздатьФайлНаСервере(ВладелецФайла, ИмяФайлаБезРасширения, Расширение, АдресДвоичныхДанных)
// Получаем двоичные данные из временного хранилища здесь, в рамках этого вызова
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресДвоичныхДанных);
Если ДвоичныеДанные = Неопределено Тогда
ВызватьИсключение "Не удалось получить данные файла из временного хранилища.";
КонецЕсли;
// 1. Формируем сведения о файле для создания/обновления
// Обратите внимание: для ДО 3.0 структура сведений может быть специфичной
СведенияОФайле = РаботаСФайламиКлиентСервер.СведенияОФайле(ИмяФайлаБезРасширения, Расширение);
// Важно: помещаем данные во временное хранилище СНОВА, но уже внутри этого контекста,
// либо используем переданный адрес, если уверены, что он жив.
// Лучше перестраховаться и поместить локально для методов БСП/ДО:
АдресДляЗаписи = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
СведенияОФайле.АдресВременногоХранилищаФайла = АдресДляЗаписи;
// Для полнотекстового поиска можно добавить и текст (опционально)
// СведенияОФайле.АдресВременногоХранилищаТекста = ...;
// 2. Поиск существующего файла
ДанныеФайловДляПоиска = Новый Массив;
СтруктураПоиска = Новый Структура;
СтруктураПоиска.Вставить("ПолноеНаименование", ИмяФайлаБезРасширения);
СтруктураПоиска.Вставить("ТекущаяВерсияРасширение", Расширение); // Расширение без точки
ДанныеФайловДляПоиска.Добавить(СтруктураПоиска);
// Используем стандартный метод поиска ДО
НайденныеФайлы = РаботаСФайламиВызовСервера.НайтиФайлыПоПолнымИменамСРасширением(ДанныеФайловДляПоиска, ВладелецФайла);
СуществующийФайлСсылка = Неопределено;
// Разбираем результат поиска. Метод может вернуть Соответствие или Массив.
// Обычно это Соответствие, где Ключ - данные поиска, Значение - ссылка на файл или массив ссылок.
Для Каждого ЭлементПоиска Из НайденныеФайлы Цикл
Если ЗначениеЗаполнено(ЭлементПоиска.Значение) Then
// Если найдено несколько, берем первый (или реализуем логику выбора)
Если ТипЗнч(ЭлементПоиска.Значение) = Тип("Массив") И ЭлементПоиска.Значение.Количество() > 0 Тогда
СуществующийФайлСсылка = ЭлементПоиска.Значение[0];
ИначеЕсли ТипЗнч(ЭлементПоиска.Значение) = Тип("СправочникСсылка.Файлы") Тогда
СуществующийФайлСсылка = ЭлементПоиска.Значение;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// 3. Логика: Обновление или Создание
Если ЗначениеЗаполнено(СуществующийФайлСсылка) Тогда
// ВЕТКА ОБНОВЛЕНИЯ
// В ДО 3.0 нельзя просто перезаписать файл, нужно создать ВЕРСИЮ.
// Проверяем, не занят ли файл
Если РаботаСФайлами.ФайлЗанят(СуществующийФайлСсылка) Тогда
ВызватьИсключение НСтр("ru = 'Файл занят другим пользователем, обновление невозможно.'");
КонецЕсли;
// Метод создания версии
// ВладелецФайлов здесь не нужен, так как мы обновляем конкретный файл
РаботаСФайламиВызовСервера.СоздатьВерсию(СуществующийФайлСсылка, СведенияОФайле);
Иначе
// ВЕТКА СОЗДАНИЯ
// Создаем новый файл, привязанный к владельцу
НовыйФайлСсылка = РаботаСФайламиВызовСервера.СоздатьФайлСВерсией(ВладелецФайла, СведенияОФайле);
КонецЕсли;
КонецПроцедуры
1. РаботаСФайламиВызовСервера.СоздатьФайлСВерсией
Этот метод используется только для создания нового элемента справочника Файлы. Он принимает ВладелецФайла (ссылку на документ или папку) и структуру сведений, содержащую адрес контента. В результате создается карточка файла и его первая активная версия.
2. РаботаСФайламиВызовСервера.СоздатьВерсию
Этот метод используется для обновления. Ему не нужен владелец (документ), ему нужна ссылка на сам справочник Файлы (переменная СуществующийФайлСсылка в нашем примере). Он берет существующую карточку файла и добавляет в регистр версий новую запись, делая её текущей. Именно этот метод нужно вызывать, если поиск вернул результат.
3. НайтиФайлыПоПолнымИменамСРасширением
Этот метод позволяет найти файлы по имени среди подчиненным конкретному владельцу. Обратите внимание, что расширение нужно передавать без точки (например, "pdf", а не ".pdf"), иначе поиск может не сработать корректно.
Если вы все же сталкиваетесь с ошибками, используйте следующие приемы:
СоздатьВерсию или СоздатьФайлСВерсией и проверьте значение во временном хранилище через "Вычислить выражение" -> ПолучитьИзВременногоХранилища(СведенияОФайле.АдресВременногоХранилищаФайла). Если там Неопределено — значит, адрес "протух".Главный секрет успешной работы с файлами в 1С:Документооборот 3.0 при программном добавлении — это контроль жизненного цикла временного хранилища и четкое разделение методов создания нового файла и добавления новой версии. Объединяя логику поиска и записи в один серверный вызов, мы гарантируем целостность данных и высокую скорость работы алгоритма.