Работа с внешними файлами, такими как PDF-заявки от клиентов или сканы договоров, является стандартной задачей для современного разработчика 1С. В управляемых формах этот процесс имеет свои архитектурные особенности, связанные с разделением выполнения кода на клиентскую и серверную части. Рассмотрим подробно, как организовать выбор файла на компьютере пользователя, его загрузку в базу данных и последующую выгрузку в произвольный каталог.
Прежде чем перейти к написанию кода, проанализируем, как данные перемещаются между пользователем и сервером. В 1С:Предприятии 8.3 работа с файлами строится по следующей цепочке: Клиент (Диалог выбора) -> Временное хранилище -> Сервер (Реквизит объекта) -> База данных. При выгрузке процесс повторяется в обратном порядке.
Важно помнить, что на стороне клиента мы имеем доступ к файловой системе пользователя, но не имеем доступа к базе данных. На сервере же ситуация обратная: есть полный доступ к данным, но нет прямого доступа к диску пользователя. Для связи этих двух миров используется Временное хранилище.
Для начала разберем, как подготовить форму. Чтобы пользователь мог инициировать выбор файла, мы можем использовать поле ввода или отдельную кнопку. Если используется поле ввода (например, для отображения имени файла), необходимо в свойствах элемента формы установить параметр КнопкаВыбора в значение "Да". Это позволит нам использовать событие ПриНачалеВыбора.
В этом обработчике мы должны сбросить стандартную обработку и вызвать свой программный код. Рассмотрим пример настройки события:
&НаКлиенте
Процедура ИмяФайлаПриНачалеВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
ЗагрузитьФайлНаСервер();
КонецПроцедуры
Рассмотрим метод загрузки файла с использованием временного хранилища. Мы будем использовать асинхронный подход, который является стандартом для современных версий платформы. Это позволяет интерфейсу не "зависать" во время передачи больших PDF-документов без использования БСП.
Разберем пример реализации процедуры загрузки:
&НаКлиенте
Процедура Загрузить(Команда)
// Проверяем наличие расширения работы с файлами (актуально для веб-клиента)
ОписаниеОповещения = Новый ОписаниеОповещения("ЗагрузитьЗавершение", ЭтаФорма);
// Параметры диалога выбора файла
ПараметрыДиалога = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
ПараметрыДиалога.Фильтр = "Документы PDF (*.pdf)|*.pdf";
ПараметрыДиалога.Заголовок = "Выберите файл заявки";
НачатьПомещениеФайла(ОписаниеОповещения, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ЗагрузитьЗавершение(Результат, Адрес, ВыбранныйФайл, ДополнительныеПараметры) Экспорт
Если НЕ Результат Тогда
Возврат;
КонецЕсли;
// Получаем информацию о выбранном файле
ОбъектФайл = Новый Файл(ВыбранныйФайл);
Объект.ИмяФайла = ОбъектФайл.Имя;
Объект.РасширениеФайла = ОбъектФайл.Расширение;
// Сохраняем адрес во временном хранилище для последующей записи на сервере
АдресЗагруженногоФайла = Адрес;
Модифицированность = Истина;
КонецПроцедуры
После того как файл попал во временное хранилище, его необходимо физически записать в реквизит объекта. Обычно для этого используется реквизит с типом ХранилищеЗначения. Проанализируем ситуацию: передать само ХранилищеЗначения с клиента на сервер напрямую нельзя, поэтому мы используем адрес временного хранилища.
Оптимальное место для этого кода — обработчик ПередЗаписьюНаСервере:
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Проверяем, был ли загружен новый файл
Если ЭтоАдресВременногоХранилища(АдресЗагруженногоФайла) Тогда
// Извлекаем двоичные данные из хранилища
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресЗагруженногоФайла);
// Помещаем в реквизит объекта
ТекущийОбъект.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанные);
КонецЕсли;
КонецПроцедуры
Теперь выясним, как решить обратную задачу: взять PDF-файл из базы и сохранить его на компьютер пользователя. Здесь часто возникает ошибка "Значение данного типа невозможно преобразовать для передачи как файл". Она обычно связана с тем, что разработчик пытается передать пустую ссылку или неопределенный объект в метод получения файла.
Рассмотрим надежный алгоритм выгрузки и конвертации:
&НаСервере
Функция ПодготовитьФайлКВыгрузке()
// Преобразуем данные из реквизита в двоичные данные и кладем в хранилище
ТекущийОбъект = РеквизитФормыВЗначение("Объект");
Если ТекущийОбъект.ДанныеФайла = Неопределено Тогда
Возврат "";
КонецЕсли;
ДвоичныеДанные = ТекущийОбъект.ДанныеФайла.Получить();
Если ДвоичныеДанные = Неопределено Тогда
Возврат "";
КонецЕсли;
Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура Выгрузить(Команда)
Адрес = ПодготовитьФайлКВыгрузке();
Если ПустаяСтрока(Адрес) Тогда
Сообщить("Файл не найден в базе данных!");
Возврат;
КонецЕсли;
// Формируем описание файла для получения
ИмяСРасширением = Объект.ИмяФайла;
ОписаниеФайла = Новый ОписаниеПередаваемогоФайла(ИмяСРасширением, Адрес);
МассивФайлов = Новый Массив;
МассивФайлов.Добавить(ОписаниеФайла);
// Открываем диалог сохранения
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
Диалог.Заголовок = "Выберите путь для сохранения PDF";
Диалог.ПолноеИмяФайла = ИмяСРасширением;
НачатьПолучениеФайлов(Неопределено, МассивФайлов, Диалог, Истина);
КонецПроцедуры
Проанализируем ситуацию, когда файлы должны храниться не в самой базе (чтобы не раздувать ее объем), а на общем сетевом диске. В этом случае программисту важно учитывать права доступа. Если код выполняется на сервере (в контексте серверного вызова), то права на запись в сетевую папку должны быть у учетной записи, под которой запущена служба 1C:Enterprise Server (обычно это USR1CV8).
Важный нюанс: используйте только UNC-пути формата \\ServerName\SharedFolder\File.pdf. Использование букв дисков (например, Z:\Files\) крайне не рекомендуется, так как сетевые диски подключаются для конкретного сеанса пользователя и могут быть недоступны серверу 1С.
Для создания каталогов и проверки их существования используйте следующие конструкции:
// Пример на сервере
ПутьККаталогу = "\\FilesServer\Documents\2023\Applications\";
Если НЕ ФайлНаСервере.Существует(ПутьККаталогу) Тогда
СоздатьКаталог(ПутьККаталогу);
КонецЕсли;
ДвоичныеДанные.Записать(ПутьККаталогу + ИмяФайла);
Начиная с версии платформы 8.3.15, 1С рекомендует отказываться от ОписаниеОповещения в пользу использования конструкции Ждать (Promises). Посмотрим, как лаконично выглядит тот же процесс загрузки PDF:
&НаКлиенте
Асинх Процедура ЗагрузитьАсинхронно()
Результат = Ждать ПоместитьФайлАсинх(, , , Истина, УникальныйИдентификатор);
Если Результат <> Неопределено Тогда
// Обработка полученного адреса хранилища
АдресЗагруженногоФайла = Результат.Адрес;
// ... дальнейшая логика
КонецЕсли;
КонецПроцедуры
Этот подход делает код более линейным и понятным, исключая необходимость создавать множество процедур-завершений (callback-функций). Мы рекомендуем переходить на этот стандарт при первой возможности.
Если ваша конфигурация построена на базе Библиотеки стандартных подсистем (БСП), рассмотрим возможность использования подсистемы "Работа с файлами". Вместо написания кода вручную, вы можете просто включить ваш документ в состав определяемого типа ВладелецФайлов. Это даст вам:
Таким образом, для простых самописных конфигураций вполне подходят методы с ХранилищемЗначения, но для крупных корпоративных систем оптимальным выбором будет внедрение функционала БСП, что сэкономит десятки часов разработки и тестирования.