Как сохранить файл изображения из справочника "НоменклатураПрисоединенныеФайлы" в 1С:Розница 3 на диск?

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

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

Изначально, разработчики, сталкивающиеся с этой задачей, пытаются напрямую обратиться к полю ФайлХранилище справочника НоменклатураПрисоединенныеФайлы и использовать его метод Получить(). Однако, как показывает практика и опыт многих разработчиков, такой подход зачастую приводит к тому, что метод Получить() возвращает Неопределено, или же полученный объект не имеет метода Записать(). Это связано с тем, что в современных типовых конфигурациях 1С для хранения двоичных данных файлов используются более сложные механизмы, часто включающие регистры сведений или внешние тома, а не прямое хранение в поле справочника.

Наиболее надежный и рекомендуемый способ работы с присоединенными файлами в 1С — это использование стандартной подсистемы "Работа с файлами" (БСП). Эта подсистема предоставляет универсальные методы для получения и сохранения файлов, абстрагируя детали их фактического хранения.

Разбираем решение по шагам:

  1. Получение ссылки на присоединенный файл.

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

    Предположим, у нас есть ссылка на элемент номенклатуры (Имя_Т), для которого мы хотим найти присоединенные файлы. Мы можем использовать следующий серверный код:

    
    &НаСервере
    Функция НайтиПрисоединенныеФайлыНоменклатуры(СсылкаНоменклатуры)
        Запрос = Новый Запрос;
        Запрос.Текст =
            "ВЫБРАТЬ
            |    НоменклатураПрисоединенныеФайлы.Ссылка КАК СсылкаНаФайл,
            |    НоменклатураПрисоединенныеФайлы.ИмяФайла КАК ИмяФайла,
            |    НоменклатураПрисоединенныеФайлы.Размер КАК Размер,
            |    НоменклатураПрисоединенныеФайлы.ТипХраненияФайла КАК ТипХраненияФайла
            |ИЗ
            |    Справочник.НоменклатураПрисоединенныеФайлы КАК НоменклатураПрисоединенныеФайлы
            |ГДЕ
            |    НоменклатураПрисоединенныеФайлы.ВладелецФайла = &ВладелецФайла";
    
        Запрос.УстановитьПараметр("ВладелецФайла", СсылкаНоменклатуры);
        
        РезультатЗапроса = Запрос.Выполнить();
        Если РезультатЗапроса.Пустой() Тогда
            Возврат Неопределено;
        КонецЕсли;
    
        Возврат РезультатЗапроса.Выгрузить(); // Возвращаем табличную часть с найденными файлами
    КонецФункции
    

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

  2. Получение двоичных данных файла.

    Как мы уже выяснили, прямой доступ через ФайлХранилище.Получить() может не работать. Для получения двоичных данных файла мы будем использовать функцию РаботаСФайлами.ПолучитьДвоичныеДанныеФайла() из стандартной библиотеки подсистем (БСП). Эта функция принимает в качестве параметра ссылку на присоединенный файл (то есть ссылку на элемент справочника НоменклатураПрисоединенныеФайлы) и возвращает объект типа ДвоичныеДанные.

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

    
    &НаСервере
    Функция ПолучитьДвоичныеДанныеПрисоединенногоФайла(СсылкаНаПрисоединенныйФайл)
        ДвоичныеДанныеФайла = РаботаСФайлами.ПолучитьДвоичныеДанныеФайла(СсылкаНаПрисоединенныйФайл);
        Если ДвоичныеДанныеФайла = Неопределено Тогда
            Сообщить("Не удалось получить двоичные данные файла.");
        КонецЕсли;
        Возврат ДвоичныеДанныеФайла;
    КонецФункции
    

    Теперь, когда у нас есть объект ДвоичныеДанные, мы можем его сохранить.

  3. Определение имени файла для сохранения.

    Чтобы сохранить файл с оригинальным именем и расширением, нам нужно получить его из справочника НоменклатураПрисоединенныеФайлы. Как правило, там есть поле ИмяФайла.

    Мы уже включаем НоменклатураПрисоединенныеФайлы.ИмяФайла в наш запрос, поэтому у нас будет доступ к этому имени.

  4. Сохранение файла на диск. Серверный вызов.

    Если код выполняется на сервере (например, в фоновом задании или в серверной процедуре), файл будет сохранен на диске сервера 1С:Предприятия. Здесь мы можем столкнуться с проблемой прав доступа, о которой часто упоминают в форумах ("Отказано в доступе"). Попытки сохранить файл в корень диска C:\ или в другие защищенные системные каталоги обычно заканчиваются неудачей, так как у учетной записи, под которой работает сервер 1С, может не быть необходимых прав.

    Мы рекомендуем использовать каталог временных файлов сервера, к которому обычно есть необходимые права. Для этого используем функцию ПолучитьКаталогВременныхФайлов().

    
    &НаСервере
    Процедура СохранитьФайлНаСервере(СсылкаНаПрисоединенныйФайл, ИмяФайла)
        ДвоичныеДанные = ПолучитьДвоичныеДанныеПрисоединенногоФайла(СсылкаНаПрисоединенныйФайл);
    
        Если ДвоичныеДанные <> Неопределено Тогда
            КаталогДляСохранения = ПолучитьКаталогВременныхФайлов(); // Каталог на сервере
            ПолноеИмяФайла = КаталогДляСохранения + ИмяФайла;
            
            Попытка
                ДвоичныеДанные.Записать(ПолноеИмяФайла);
                Сообщить("Файл '" + ИмяФайла + "' успешно сохранен на сервере по пути: " + ПолноеИмяФайла);
            Исключение
                Сообщить("Ошибка при сохранении файла на сервере: " + ОписаниеОшибки());
            КонецПопытки;
        КонецЕсли;
    КонецПроцедуры
    

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

  5. Сохранение файла на диск. Клиентский вызов с выбором пути.

    Часто требуется сохранить файл на локальный компьютер пользователя. В этом случае мы не можем просто вызвать метод Записать() на клиенте, так как двоичные данные находятся на сервере. Нам потребуется организовать клиент-серверное взаимодействие:

    1. На сервере получаем двоичные данные файла.
    2. Помещаем эти данные во временное хранилище на сервере.
    3. Передаем адрес временного хранилища на клиент.
    4. На клиенте инициируем процесс получения файла из временного хранилища, позволяя пользователю выбрать место для сохранения с помощью ДиалогВыбораФайла.

    Рассмотрим серверный и клиентский код для этого сценария:

    Серверный код для подготовки файла:

    
    &НаСервере
    Функция ПодготовитьФайлДляСохраненияНаКлиенте(СсылкаНаПрисоединенныйФайл, ИмяФайла)
        ДвоичныеДанные = ПолучитьДвоичныеДанныеПрисоединенногоФайла(СсылкаНаПрисоединенныйФайл);
    
        Если ДвоичныеДанные <> Неопределено Тогда
            // Помещаем двоичные данные во временное хранилище
            // Второй параметр - это уникальный идентификатор, который будет использоваться для ссылки на данные
            АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор.НовыйУникальныйИдентификатор()); 
            Возврат АдресХранилища;
        КонецЕсли;
        Возврат Неопределено;
    КонецФункции
    

    Клиентский код для сохранения файла:

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

    
    &НаКлиенте
    Процедура СохранитьФайлНаКлиентеИзСправочника(СсылкаНаПрисоединенныйФайл, ПредлагаемоеИмяФайла)
        // 1. Вызываем серверную функцию для получения адреса временного хранилища
        АдресХранилища = ПодготовитьФайлДляСохраненияНаКлиенте(СсылкаНаПрисоединенныйФайл, ПредлагаемоеИмяФайла);
    
        Если АдресХранилища <> Неопределено Тогда
            // 2. Создаем диалог выбора файла для сохранения
            Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
            Диалог.ПолноеИмяФайла = ПредлагаемоеИмяФайла; // Устанавливаем предлагаемое имя файла
            Диалог.Заголовок = "Выберите место для сохранения файла";
            
            Если Диалог.Выбрать() Тогда
                // 3. Запускаем асинхронное получение файла
                ОписаниеОповещения = Новый ОписаниеОповещения("ПослеЗавершенияПолученияФайлаНаКлиенте", ЭтотОбъект);
                ОписаниеПередаваемогоФайла = Новый ОписаниеПередаваемогоФайла(АдресХранилища, Диалог.ПолноеИмяФайла);
                НачатьПолучениеФайла(ОписаниеПередаваемогоФайла, ОписаниеОповещения);
            Иначе
                Сообщить("Сохранение файла отменено пользователем.");
            КонецЕсли;
        Иначе
            Сообщить("Не удалось подготовить файл для сохранения.");
        КонецЕсли;
    КонецПроцедуры
    
    // 4. Процедура-обработчик после завершения получения файла
    &НаКлиенте
    Процедура ПослеЗавершенияПолученияФайлаНаКлиенте(Результат, ДополнительныеПараметры) Экспорт
        Если Результат.СтатусВозврата = СтатусВозвратаФайла.Ок Тогда
            Сообщить("Файл успешно сохранен: " + Результат.ПолноеИмяФайла);
        Иначе
            Сообщить("Ошибка сохранения файла: " + Результат.ОписаниеОшибки);
        КонецЕсли;
    КонецПроцедуры
    
  6. Комплексный пример реализации (фрагмент кода):

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

    Предположим, у нас есть кнопка на форме документа "Расходная накладная", и мы хотим сохранить изображения номенклатуры, присутствующей в табличной части.

    
    // В обработчике команды на клиенте
    &НаКлиенте
    Процедура СохранитьИзображенияНоменклатуры(Команда)
        Для Каждого СтрокаТовар Из Объект.Запасы Цикл // Объект - это документ "Расходная накладная"
            // Получаем ссылку на номенклатуру из текущей строки
            СсылкаНоменклатуры = СтрокаТовар.Номенклатура;
    
            // Вызываем серверную функцию для поиска присоединенных файлов
            // и получаем таблицу значений с их ссылками и именами
            ТаблицаФайлов = НайтиПрисоединенныеФайлыНоменклатуры(СсылкаНоменклатуры);
            
            Если ТаблицаФайлов <> Неопределено Тогда
                Для Каждого СтрокаФайла Из ТаблицаФайлов Цикл
                    СсылкаНаПрисоединенныйФайл = СтрокаФайла.СсылкаНаФайл;
                    ИмяФайлаДляСохранения = СтрокаФайла.ИмяФайла;
                    
                    // Пример сохранения на сервере (для демонстрации, если это уместно)
                    // СохранитьФайлНаСервере(СсылкаНаПрисоединенныйФайл, ИмяФайлаДляСохранения);
                    
                    // Пример сохранения на клиенте (предпочтительный для пользователя)
                    СохранитьФайлНаКлиентеИзСправочника(СсылкаНаПрисоединенныйФайл, ИмяФайлаДляСохранения);
                    
                КонецЦикла;
            Иначе
                Сообщить("Для номенклатуры '" + СсылкаНоменклатуры.Наименование + "' присоединенные файлы не найдены.");
            КонецЕсли;
        КонецЦикла;
    КонецПроцедуры
    
    // Не забываем определить функции НайтиПрисоединенныеФайлыНоменклатуры(), 
    // ПолучитьДвоичныеДанныеПрисоединенногоФайла(), ПодготовитьФайлДляСохраненияНаКлиенте(), 
    // СохранитьФайлНаСервере() и процедуру ПослеЗавершенияПолученияФайлаНаКлиенте() на сервере/клиенте 
    // в соответствии с их директивами.
    

Основные выводы и важные моменты, которые мы рассмотрели:

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

← На главную