При разработке расширений или обработок для 1С, часто возникает задача загрузки данных из внешних файлов, таких как Excel. Однако, в клиент-серверной архитектуре 1С мы можем столкнуться с распространенной проблемой: серверный процесс 1С не имеет прямого доступа к файлам, расположенным на локальном диске клиентского компьютера. Это приводит к ошибке "Каталог не обнаружен", когда мы пытаемся прочитать файл по пути, выбранному на клиенте, но обработка файла происходит на сервере. Давайте подробно разберем, почему это происходит, и какие существуют эффективные способы решения этой задачи.
Прежде чем перейти к решениям, давайте проанализируем корневую причину возникшей проблемы. Система 1С:Предприятие в клиент-серверном режиме состоит как минимум из трех частей:
Когда мы используем функции, такие как ДиалогВыбораФайла, для выбора файла, это действие выполняется на клиентском компьютере, и возвращается путь к файлу на этом же клиентском компьютере. Однако, когда мы вызываем серверную процедуру (например, помеченную директивой &НаСервере или &НаСервереБезКонтекста) и передаем ей этот локальный путь, серверный процесс пытается найти файл по этому пути на своем диске, а не на диске клиента. Поскольку файл там отсутствует, мы получаем ошибку "Каталог не обнаружен".
Таким образом, наша задача заключается в том, чтобы безопасно и эффективно переместить или передать содержимое файла с клиентского компьютера на сервер для последующей обработки. Рассмотрим несколько способов решения этой задачи.
Этот метод является наиболее безопасным, гибким и рекомендованным для передачи двоичных данных (в том числе файлов) между клиентом и сервером в системе 1С. Временное хранилище позволяет сохранить данные на сервере (или в кластере серверов) по уникальному адресу, который затем можно использовать для доступа к этим данным из серверного кода.
На первом этапе мы должны предоставить пользователю возможность выбрать файл на его локальном компьютере, а затем поместить содержимое этого файла во временное хранилище 1С. Это делается полностью на клиентской стороне.
ДиалогВыбораФайла, чтобы пользователь выбрал необходимый Excel-файл.НачатьПомещениеФайла. Эта функция асинхронно помещает выбранный файл во временное хранилище и по завершении вызывает указанную процедуру-обработчик на клиенте, передавая ей адрес файла во временном хранилище. Также можно использовать синхронную функцию ПоместитьВоВременноеХранилище, если это допустимо для пользовательского опыта.Давайте посмотрим на пример клиентского кода:
&НаКлиенте
ПеременнаяФормы_ИмяФайла = ""; // Переменная для хранения имени выбранного файла
ПеременнаяФормы_АдресХранения = ""; // Переменная для хранения адреса файла во временном хранилище
Процедура ИмяФайлаНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь; // Отменяем стандартную обработку выбора
// Создаем описание оповещения для процедуры, которая будет вызвана после помещения файла
Описание = Новый ОписаниеОповещения("ЗакончитьВыборФайла", ЭтотОбъект);
// Начинаем помещение файла во временное хранилище.
// Третий параметр - "ИмяФайла", если не указан, берется из диалога
// Четвертый параметр - "ПомещатьВоВременноеХранилище" (Истина - по умолчанию)
// Пятый параметр - "УникальныйИдентификатор" (можно оставить пустым или указать)
НачатьПомещениеФайла(Описание,,,Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ЗакончитьВыборФайла(Результат, Адрес, Имя, ДопПараметры) Экспорт
// Эта процедура будет вызвана после того, как файл будет помещен во временное хранилище.
// Результат - Истина, если файл успешно выбран и помещен.
// Адрес - адрес файла во временном хранилище.
// Имя - исходное имя файла.
Если Результат Тогда
ПеременнаяФормы_ИмяФайла = Имя; // Сохраняем имя файла для отображения пользователю
ПеременнаяФормы_АдресХранения = Адрес; // Сохраняем адрес хранилища для передачи на сервер
Сообщить("Файл '" + Имя + "' успешно помещен во временное хранилище. Адрес: " + Адрес);
Иначе
Предупреждение("Файл не выбран или произошла ошибка при помещении во временное хранилище.");
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьЗагрузку(Команда)
Если ПеременнаяФормы_АдресХранения = "" Тогда
Предупреждение("Файл для загрузки не выбран!");
Возврат;
КонецЕсли;
// Спрашиваем пользователя, действительно ли он хочет выполнить загрузку
Оповещение = Новый ОписаниеОповещения("НачатьВыполнениеЗагрузки", ЭтаФорма);
ПоказатьВопрос(Оповещение, "Выполнить загрузку данных из файла """ + ПеременнаяФормы_ИмяФайла + """ ?", РежимДиалогаВопрос.ДаНет, , , "Начало загрузки");
КонецПроцедуры
&НаКлиенте
Процедура НачатьВыполнениеЗагрузки(Результат, Параметры) Экспорт
Если НЕ Результат = КодВозвратаДиалога.Да Тогда
Возврат; // Пользователь отказался от загрузки
КонецЕсли;
// Вызываем серверную функцию для выполнения загрузки, передавая адрес временного хранилища
РезультатЗагрузки = ВыполнитьЗагрузкуНаСервере(ПеременнаяФормы_АдресХранения);
Если ТипЗнч(РезультатЗагрузки) = Тип("Строка") Тогда
// Если серверная функция вернула строку, это, вероятно, сообщение об ошибке
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = РезультатЗагрузки;
Сообщение.Поле = "ИмяФайла"; // Можно привязать сообщение к полю на форме
Сообщение.Сообщить();
Иначе
Сообщить("Загрузка данных успешно завершена!");
КонецЕсли;
КонецПроцедуры
После того как файл помещен во временное хранилище, его адрес передается на сервер. На сервере мы можем получить двоичные данные файла и работать с ними.
ЭтоАдресВременногоХранилища.ПолучитьИзВременногоХранилища мы извлекаем двоичные данные файла.ПолучитьИмяВременногоФайла, а затем используем метод Записать() объекта ДвоичныеДанные.ТабличныйДокумент, для его чтения и обработки.УдалитьФайлы, чтобы не засорять файловую систему сервера.Посмотрим на пример серверного кода:
&НаСервереБезКонтекста
Функция ВыполнитьЗагрузкуНаСервере(АдресХранения)
// Проверяем, является ли переданный адрес адресом временного хранилища
Если Не ЭтоАдресВременногоХранилища(АдресХранения) Тогда
Возврат "Ошибка: Передан некорректный адрес временного хранилища.";
КонецЕсли;
Попытка
// Получаем двоичные данные файла из временного хранилища
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранения);
Если ДвоичныеДанные = Неопределено Тогда
Возврат "Ошибка: Не удалось получить данные из временного хранилища по адресу: " + АдресХранения;
КонецЕсли;
// Генерируем уникальное имя для временного файла на сервере.
// Предполагаем, что это Excel-файл, поэтому расширение ".xlsx" или ".xls".
// В данном примере используем ".xls" как общее для Excel.
ИмяФайлаНаСервере = ПолучитьИмяВременногоФайла(".xlsx");
// Записываем двоичные данные во временный файл на сервере
ДвоичныеДанные.Записать(ИмяФайлаНаСервере);
// Создаем объект ТабличныйДокумент для чтения Excel-файла
ТабличныйДокумент = Новый ТабличныйДокумент;
// Читаем данные из файла
ТабличныйДокумент.Прочитать(ИмяФайлаНаСервере, СпособЧтенияЗначенийТабличногоДокумента.Значение);
// После прочтения файла, он больше не нужен, удаляем его
УдалитьФайлы(ИмяФайлаНаСервере);
// Проверяем, удалось ли прочитать области листа
Если ТабличныйДокумент.Области.Количество = 0 Тогда
Возврат "Ошибка чтения файла: Файл Excel пуст или не содержит данных.";
КонецЕсли;
// Начинаем обработку данных из ТабличногоДокумента
// Здесь можно добавить логику парсинга Excel-файла и создания документов в 1С
// Пример обработки (на основе кода автора):
x = 2; // Начальная строка для чтения данных (предполагаем, что первая строка - заголовок)
ТаблицаСтрок = Новый ТаблицаЗначений;
ТаблицаСтрок.Колонки.Добавить("НоменклатураН");
ТаблицаСтрок.Колонки.Добавить("Количество");
Пока ТабличныйДокумент.Область("R" + Строка(x) + "C1").Текст <> "" Цикл // Читаем строки, пока есть данные в первой колонке
СтрокаИзExcel = Новый Структура;
Попытка
Количество = Число(СокрЛП(ТабличныйДокумент.Область("R" + Строка(x) + "C2").Текст));
Исключение
Количество = 0;
КонецПопытки;
Если Количество > 0 Тогда
ПерваяКолонка = СокрЛП(ТабличныйДокумент.Область("R" + Строка(x) + "C1").Текст);
НоваяСтрокаТаблицы = ТаблицаСтрок.Добавить();
НоваяСтрокаТаблицы.НоменклатураН = ПерваяКолонка;
НоваяСтрокаТаблицы.Количество = Количество;
КонецЕсли;
x = x + 1;
КонецЦикла;
// Теперь у нас есть ТаблицаСтрок с данными, которые можно использовать для создания документов.
// Пример: создание ЗаказовКлиента
// ... Логика создания документов "ЗаказКлиента" на основе ТаблицаСтрок ...
// Например:
Для Каждого СтрокаТЗ Из ТаблицаСтрок Цикл
// Создаем новый документ "ЗаказКлиента"
НовыйЗаказ = Документы.ЗаказКлиента.СоздатьДокумент();
НовыйЗаказ.Дата = ТекущаяДата();
// ... Заполняем остальные реквизиты документа ...
НовыйЗаказ.Организация = Справочники.Организации.НайтиПоНаименованию("НашаОрганизация"); // Пример
НовыйЗаказ.Партнер = Справочники.Партнеры.НайтиПоНаименованию("КлиентИзExcel"); // Пример
// Добавляем строку в табличную часть "Товары"
НоваяСтрокаТовары = НовыйЗаказ.Товары.Добавить();
НоваяСтрокаТовары.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрокаТЗ.НоменклатураН);
НоваяСтрокаТовары.Количество = СтрокаТЗ.Количество;
НоваяСтрокаТовары.Цена = 100; // Пример
НоваяСтроваТовары.Сумма = НоваяСтроваТовары.Количество * НоваяСтроваТовары.Цена;
// Записываем документ
НовыйЗаказ.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
Возврат "Данные успешно загружены и документы созданы!";
Исключение
// В случае ошибки, удаляем временный файл, если он был создан
Если ЗначениеЗаполнено(ИмяФайлаНаСервере) И ФайлСуществует(ИмяФайлаНаСервере) Тогда
УдалитьФайлы(ИмяФайлаНаСервере);
КонецЕсли;
Возврат "Произошла ошибка при загрузке или обработке файла: " + ОписаниеОшибки();
КонецПопытки;
КонецФункции
Преимущества временного хранилища:
Этот подход предполагает, что весь Excel-файл будет прочитан и разобран на клиентской стороне, а затем на сервер будет передан не сам файл, а уже готовая структура данных (например, таблица значений или массив структур). Серверная часть будет работать уже с этими структурированными данными, а не с файлом.
ДиалогВыбораФайла.ТабличныйДокумент (или использует COM-объект Excel, если это допустимо).ТабличныйДокумент парсятся и преобразуются в удобную для передачи структуру, например, ТаблицаЗначений или Массив из Структур.Преимущества:
Недостатки:
ТабличныйДокумент или COM-объектом Excel. В веб-клиенте работа с локальными файлами и COM-объектами ограничена или невозможна.Давайте рассмотрим пример реализации:
&НаКлиенте
Процедура MAD_ВыбратьПосле(Команда)
Режим = РежимДиалогаВыбораФайла.Открытие;
ДиалогОткрытияФайла = Новый ДиалогВыбораФайла(Режим);
ДиалогОткрытияФайла.ПолноеИмяФайла = "";
// Можно настроить фильтр для файлов Excel
// ДиалогОткрытияФайла.Фильтр = НСтр("ru = 'Ексель'; en = 'Excel'") + "(*.xlsx)|*.xlsx" + "(*.xls)|*.xls";
ДиалогОткрытияФайла.МножественныйВыбор = Ложь;
ДиалогОткрытияФайла.Заголовок = "Выберите файл Excel";
Если ДиалогОткрытияФайла.Выбрать() Тогда
ИмяВыбранногоФайла = ДиалогОткрытияФайла.ПолноеИмяФайла;
Сообщить("Выбран файл: " + ИмяВыбранногоФайла);
Попытка
// Читаем Excel-файл в ТабличныйДокумент на клиенте
ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ИмяВыбранногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
// Создаем ТаблицуЗначений для передачи на сервер
ТаблицаДанныхДляСервера = Новый ТаблицаЗначений;
ТаблицаДанныхДляСервера.Колонки.Добавить("НоменклатураН", Новый ОписаниеТипов("Строка"));
ТаблицаДанныхДляСервера.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
x = 2; // Начальная строка для чтения данных
Пока ТабДок.Область("R" + Строка(x) + "C1").Текст <> "" Цикл
Количество = 0;
Попытка
Количество = Число(СокрЛП(ТабДок.Область("R" + Строка(x) + "C2").Текст));
Исключение
// Ошибка преобразования, количество остается 0
КонецПопытки;
Если Количество > 0 Тогда
НоваяСтрока = ТаблицаДанныхДляСервера.Добавить();
НоваяСтрока.НоменклатураН = СокрЛП(ТабДок.Область("R" + Строка(x) + "C1").Текст);
НоваяСтрока.Количество = Количество;
КонецЕсли;
x = x + 1;
КонецЦикла;
// Вызываем серверную функцию для обработки ТаблицыЗначений
РезультатОбработки = ОбработатьДанныеСервере(ТаблицаДанныхДляСервера);
Если ТипЗнч(РезультатОбработки) = Тип("Строка") Тогда
Сообщить("Ошибка на сервере: " + РезультатОбработки, СтатусСообщения.Внимание);
Иначе
Сообщить("Данные успешно обработаны на сервере!");
КонецЕсли;
Исключение
Сообщить("Ошибка при чтении или обработке файла на клиенте: " + ОписаниеОшибки(), СтатусСообщения.Внимание);
КонецПопытки;
Иначе
Предупреждение("Файл не выбран!");
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ОбработатьДанныеСервере(ТаблицаДанных)
Попытка
Если ТаблицаДанных.Количество() = 0 Тогда
Возврат "Таблица данных пуста, нет ничего для обработки.";
КонецЕсли;
// Здесь мы получаем ТаблицуЗначений и работаем с ней уже на сервере
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
// ... Логика создания документов "ЗаказКлиента" на основе СтрокаДанных ...
НовыйЗаказ = Документы.ЗаказКлиента.СоздатьДокумент();
НовыйЗаказ.Дата = ТекущаяДата();
// ... Заполняем реквизиты ...
НовыйЗаказ.Организация = Справочники.Организации.НайтиПоНаименованию("НашаОрганизация"); // Пример
НовыйЗаказ.Партнер = Справочники.Партнеры.НайтиПоНаименованию("КлиентИзExcel"); // Пример
НоваяСтрокаТовары = НовыйЗаказ.Товары.Добавить();
НоваяСтрокаТовары.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрокаДанных.НоменклатураН);
НоваяСтрокаТовары.Количество = СтрокаДанных.Количество;
НоваяСтроваТовары.Цена = 100; // Пример
НоваяСтроваТовары.Сумма = НоваяСтроваТовары.Количество * НоваяСтроваТовары.Цена;
НовыйЗаказ.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
Возврат Истина; // Успех
Исключение
Возврат "Ошибка при обработке данных на сервере: " + ОписаниеОшибки();
КонецПопытки;
КонецФункции
Этот подход является наиболее прямолинейным, но он имеет существенные недостатки, связанные с безопасностью и администрированием. Суть метода заключается в том, чтобы после выбора файла на клиенте, программно скопировать его в сетевую папку, которая доступна серверу 1С.
Важные моменты для реализации:
\\ИмяСервера\ИмяОбщейПапки\.Давайте посмотрим на пример клиентского кода:
&НаКлиенте
Процедура MAD_ВыбратьПосле(Команда)
Режим = РежимДиалогаВыбораФайла.Открытие;
ДиалогОткрытияФайла = Новый ДиалогВыбораФайла(Режим);
ДиалогОткрытияФайла.ПолноеИмяФайла = "";
ДиалогОткрытияФайла.МножественныйВыбор = Ложь;
ДиалогОткрытияФайла.Заголовок = "Выберите файл";
Если ДиалогОткрытияФайла.Выбрать Тогда
ИмяФайлаНаКлиенте = ДиалогОткрытияФайла.ПолноеИмяФайла;
Сообщить("Выбран файл: " + ИмяФайлаНаКлиенте);
// Определяем путь к расшаренной папке на сервере.
// ЭТОТ ПУТЬ ДОЛЖЕН БЫТЬ ДОСТУПЕН КАК КЛИЕНТУ (ДЛЯ ЗАПИСИ), ТАК И СЕРВЕРУ (ДЛЯ ЧТЕНИЯ И ЗАПИСИ)
ПутьКРасшареннойПапкеНаСервере = "\\ИмяВашегоСервера\ИмяОбщейПапкиДляЗагрузки\"; // Укажите реальный UNC-путь!
// Получаем только имя файла из полного пути
ИмяФайлаБезПути = Новый Файл(ИмяФайлаНаКлиенте).Имя;
// Формируем полный путь для нового файла в расшаренной папке
ПутьНаСервереДляКопирования = ПутьКРасшареннойПапкеНаСервере + ИмяФайлаБезПути;
Попытка
// Копируем файл с клиента в расшаренную папку на сервере
КопироватьФайл(ИмяФайлаНаКлиенте, ПутьНаСервереДляКопирования);
Сообщить("Файл успешно скопирован на сервер в: " + ПутьНаСервереДляКопирования);
// Теперь вызываем серверную процедуру, передавая ей путь к файлу в расшаренной папке
РезультатЗагрузки = MAD_ВыбратьПослеНаСервере(ПутьНаСервереДляКопирования);
Если ТипЗнч(РезультатЗагрузки) = Тип("Строка") Тогда
Сообщить("Ошибка на сервере: " + РезультатЗагрузки, СтатусСообщения.Внимание);
Иначе
Сообщить("Данные успешно загружены и документы созданы!");
КонецЕсли;
Исключение
Сообщить("Ошибка при копировании файла или обработке на сервере: " + ОписаниеОшибки(), СтатусСообщения.Внимание);
КонецПопытки;
Иначе
Предупреждение("Файл не выбран!");
КонецЕсли;
КонецПроцедуры
// Серверная процедура, которая теперь читает файл из расшаренной папки
&НаСервере
Процедура MAD_ВыбратьПослеНаСервере(ИмяФайлаНаСервере)
ТабДок = Новый ТабличныйДокумент;
Попытка
// Сервер теперь видит этот путь, так как он сетевой и доступен
ТабДок.Прочитать(ИмяФайлаНаСервере, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
Сообщить("Ошибка при вызове метода контекста (Прочитать): " + ОписаниеОшибки(), СтатусСообщения.Внимание);
Возврат;
КонецПопытки;
x = 2; // начальная строка
ТаблицаДанныхДляСоздания = Новый ТаблицаЗначений;
ТаблицаДанныхДляСоздания.Колонки.Добавить("НоменклатураН");
ТаблицаДанныхДляСоздания.Колонки.Добавить("Количество");
Пока ТабДок.Область("R" + Строка(x) + "C1").Текст <> "" Цикл //будем читать строки пока есть данные в первой колонке
Количество = 0;
Попытка
Количество = Число(СокрЛП(ТабДок.Область("R" + Строка(x) + "C2").Текст));
Исключение
Количество = 0;
КонецПопытки;
Если Количество > 0 Тогда
НоваяСтрока = ТаблицаДанныхДляСоздания.Добавить();
НоваяСтрока.НоменклатураН = СокрЛП(ТабДок.Область("R" + Строка(x) + "C1").Текст);
НоваяСтрока.Количество = Количество;
КонецЕсли;
x = x + 1;
КонецЦикла;
// ... Логика создания документов "ЗаказКлиента" на основе ТаблицаДанныхДляСоздания ...
// Например:
Для Каждого СтрокаТЗ Из ТаблицаДанныхДляСоздания Цикл
НовыйЗаказ = Документы.ЗаказКлиента.СоздатьДокумент();
НовыйЗаказ.Дата = ТекущаяДата();
НовыйЗаказ.Организация = Справочники.Организации.НайтиПоНаименованию("НашаОрганизация");
НовыйЗаказ.Партнер = Справочники.Партнеры.НайтиПоНаименованию("КлиентИзExcel");
НоваяСтрокаТовары = НовыйЗаказ.Товары.Добавить();
НоваяСтрокаТовары.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрокаТЗ.НоменклатураН);
НоваяСтроваТовары.Количество = СтрокаТЗ.Количество;
НоваяСтроваТовары.Цена = 100;
НоваяСтроваТовары.Сумма = НоваяСтроваТовары.Количество * НоваяСтроваТовары.Цена;
НовыйЗаказ.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
// После обработки, можно удалить файл из расшаренной папки
Попытка
УдалитьФайлы(ИмяФайлаНаСервере);
Сообщить("Временный файл '" + ИмяФайлаНаСервере + "' успешно удален с сервера.");
Исключение
Сообщить("Предупреждение: Не удалось удалить временный файл '" + ИмяФайлаНаСервере + "'. " + ОписаниеОшибки(), СтатусСообщения.Внимание);
КонецПопытки;
КонецПроцедуры
Недостатки этого подхода:
Независимо от выбранного подхода, при работе с файлами в клиент-серверном режиме 1С, всегда следует учитывать следующие моменты:
&НаКлиенте) и какой на сервере (&НаСервере или &НаСервереБезКонтекста). Функции для работы с пользовательским интерфейсом (например, Сообщить, Предупреждение, ПоказатьВопрос), а также с локальными файлами клиента (ДиалогВыбораФайла, Файл) доступны только на клиенте. Функции для работы с базой данных, серверными файлами и временным хранилищем доступны только на сервере.Попытка...Исключение...КонецПопытки при работе с файловой системой, сетевыми ресурсами или конвертацией данных. Это позволит корректно обрабатывать ситуации, когда файл не найден, поврежден, имеет некорректный формат или возникли проблемы с правами доступа.УдалитьФайлы(). Это предотвратит засорение файловой системы сервера и потенциальные проблемы с производительностью.Сообщить(), Предупреждение(), ПоказатьВопрос(), а также индикаторы выполнения, если процесс длительный..xls (старый формат Excel) и .xlsx (новый формат Office Open XML). Объект ТабличныйДокумент в 1С обычно хорошо работает с обоими форматами, но при создании временных файлов убедитесь, что расширение соответствует содержимому.Загрузка заказов из Excel с клиентского компьютера в клиент-серверной архитектуре 1С — это стандартная задача, которая требует понимания принципов взаимодействия клиента и сервера. Мы рассмотрели три основных подхода:
Основываясь на рекомендациях 1С и опыте разработки, мы советуем отдавать предпочтение методу с использованием временного хранилища. Этот подход обеспечивает наилучший баланс между функциональностью, безопасностью и надежностью, что критически важно для промышленных решений.
Выбирая подходящий метод, всегда оценивайте конкретные требования вашего проекта, объем данных, среду выполнения (тонкий/веб-клиент) и существующие политики безопасности вашей организации.