Как программно добавить файл или обновить его версию в 1С:Документооборот 3.0?

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

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

Постановка задачи и анализ проблемы

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

Логика работы должна быть следующей:

  1. Мы формируем двоичные данные файла (например, через механизм визуализации).
  2. Ищем, прикреплен ли уже к документу файл с таким именем.
  3. Если файл найден — создаем его новую версию.
  4. Если файл не найден — создаем новый файл.

На практике при реализации этого алгоритма разработчики часто сталкиваются с ошибками вида:

Давайте разберемся, почему эти ошибки возникают и как написать код, который будет работать стабильно.

Почему пропадают данные? (Ошибка контекста)

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

Причина кроется в механизме работы ВременноеХранилище. Когда мы помещаем данные во временное хранилище с помощью метода ПоместитьВоВременноеХранилище, мы получаем адрес. Если мы не передаем третьим параметром Уникальный Идентификатор формы (UUID), то данные во временном хранилище живут только в течение одного серверного вызова.

Рассмотрим ошибочный сценарий:

  1. На сервере получаем двоичные данные и помещаем их во временное хранилище (возвращаем адрес на клиент).
  2. На клиенте формируем структуру для поиска.
  3. Вызываем серверную функцию для поиска файла (контекстный вызов завершается).
  4. Снова вызываем сервер для создания версии, передавая адрес из п.1.

К моменту выполнения пункта 4 адрес временного хранилища, полученный в пункте 1, может быть уже невалиден или очищен платформой, так как произошел разрыв контекста. В результате в методы записи файла передается Неопределено вместо двоичных данных, что вызывает ошибку чтения свойства Размер().

Правильное архитектурное решение

Чтобы избежать потери данных и лишних вызовов между клиентом и сервером, мы должны инкапсулировать (объединить) всю логику работы с файлом в одну серверную процедуру. Мы передадим на сервер ссылку на документ-владелец и сами данные, а внутри процедуры решим, создавать файл или обновлять версию.

Давайте пошагово реализуем правильный алгоритм.

Шаг 1. Подготовка данных

Сначала нам нужно получить сам файл. В ДО 3.0 часто используется механизм визуализации электронной подписи или просто формирование печатного образа. Получим двоичные данные PDF.


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

// Получаем имя временного файла
FilePDFDest = ПолучитьИмяВременногоФайла("pdf");
 
// Извлекаем двоичные данные из результата визуализации
ДвоичныеДанныеPDF = РаботаСФайламиВызовСервера.ПолучитьДвоичныеДанныеФайла(ПолучаемФайлСЭП);

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

Шаг 2. Основная серверная процедура

Теперь создадим процедуру, которая примет данные и выполнит всю работу. Это ключевое место решения. Мы не будем возвращать управление на клиент между поиском и записью.


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

Разбор ключевых методов

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

2. РаботаСФайламиВызовСервера.СоздатьВерсию
Этот метод используется для обновления. Ему не нужен владелец (документ), ему нужна ссылка на сам справочник Файлы (переменная СуществующийФайлСсылка в нашем примере). Он берет существующую карточку файла и добавляет в регистр версий новую запись, делая её текущей. Именно этот метод нужно вызывать, если поиск вернул результат.

3. НайтиФайлыПоПолнымИменамСРасширением
Этот метод позволяет найти файлы по имени среди подчиненным конкретному владельцу. Обратите внимание, что расширение нужно передавать без точки (например, "pdf", а не ".pdf"), иначе поиск может не сработать корректно.

Рекомендации по отладке

Если вы все же сталкиваетесь с ошибками, используйте следующие приемы:

Заключение

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

← На главную