МЕТОДЫ
В обработку добавлены методы: загрузка файла с клиента на сервер, чтение Excel файла через построитель, процедура нересурсоемкого серверного ожидания в целях демонстрации на любой базе без требования к конкретным данным и приближенности примера к реальным задачам.
СРЕДА
Тестировалось на конфигурации Управление Торговлей 11.4.13.227, версия БСП 3.1.3.548, платформа 8.3.17.1549
ДЕМОНСТРАЦИЯ
Нажать на кнопку "Обзор" и выбрать любой файл Excel с клиента, версия формата 2007.
КОД
Модуль объекта
Функция СведенияОВнешнейОбработке() Экспорт
ИмяОбработки = Метаданные().Имя;
Синоним = Метаданные().Синоним;
Синоним = ?(ПустаяСтрока(Синоним), ИмяОбработки, Синоним);
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке();
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительнаяОбработка();
ПараметрыРегистрации.Наименование = Синоним;
ПараметрыРегистрации.Версия = "1.0";
ПараметрыРегистрации.Информация = Синоним;
ПараметрыРегистрации.БезопасныйРежим = Ложь;
ТаблицаКоманд = ПараметрыРегистрации.Команды;
Команда = ТаблицаКоманд.Добавить();
Команда.Идентификатор = "ОткрытьОбработку";
Команда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыОткрытиеФормы();
Команда.Представление = Синоним + " (открыть форму)";
Возврат ПараметрыРегистрации;
КонецФункции
Процедура ЗагрузитьДанные(ПараметрыДлительнойОперации, АдресРезультата) Экспорт
КоличествоСтрокВычисленияПрогресса = 1;
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
ДлительныеОперации.СообщитьПрогресс(0, "Загрузка файла в систему...");
ПараметрыДлительнойОперации.ДвоичныеДанные.Записать(ИмяВременногоФайла);
ТабличныйДокумент.Прочитать(ИмяВременногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
ВысотаТаблицыБезЗаголовка = ТабличныйДокумент.ВысотаТаблицы - 1;
ИсточникДанных = Новый ОписаниеИсточникаДанных(ТабличныйДокумент.Область(1, 1, ТабличныйДокумент.ВысотаТаблицы, ТабличныйДокумент.ШиринаТаблицы));
ПостроительОтчетов = Новый ПостроительОтчета;
ПостроительОтчетов.ИсточникДанных = ИсточникДанных;
ПостроительОтчетов.Выполнить();
ВыборкаДетальныеЗаписи = ПостроительОтчетов.Результат.Выбрать();
ТекущаяСтрока = 0;
СчетчикПрогресса = 0;
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
КакаяТоПроцедураОбработкиСтроки(ВыборкаДетальныеЗаписи);
ТекущаяСтрока = ТекущаяСтрока + 1;
СчетчикПрогресса = СчетчикПрогресса + 1;
Если СчетчикПрогресса = КоличествоСтрокВычисленияПрогресса Тогда
Процент = Формат(ТекущаяСтрока / ВысотаТаблицыБезЗаголовка * 100, "ЧДЦ=2");
ДлительныеОперации.СообщитьПрогресс(Процент, СтрШаблон("Обработка %1 строки из %2", ТекущаяСтрока, ВысотаТаблицыБезЗаголовка));
СчетчикПрогресса = 0;
КонецЕсли;
КонецЦикла;
ДанныеРезультата = Новый Структура("ВсегоСтрок", ТекущаяСтрока);
ПоместитьВоВременноеХранилище(ДанныеРезультата, АдресРезультата);
КонецПроцедуры
Процедура КакаяТоПроцедураОбработкиСтроки(ВыборкаДетальныеЗаписи)
Ждать(1);
КонецПроцедуры
Процедура Ждать(КоличествоСекунд)
Попытка
Соединение = Новый HTTPСоединение("127.0.0.0",,,,,КоличествоСекунд);
Соединение.Получить(Новый HTTPЗапрос());
Исключение
Конецпопытки;
КонецПроцедуры
Обратите внимание на переменную КоличествоСтрокВычисленияПрогресса, которая определяет интервал между вычислением и отправкой прогресса на клиент. Здесь намеренно значение равно 1 в целях демонстрации.
На рабочей задаче обязательно задайте максимальный интервал, который будет удовлетворять пользователя в целях оптимизации, например 100.
Используется чтение табличным документом из файла, т.к. на данный момент Excel читается только так. Если же вы работаете с форматами MXL, ODS, то используйте чтение из потока в целях оптимизации. В процедуре КакаяТоПроцедураОбработкиСтроки в целях демонстрации выполняется ожидание длиной в 1 секунду.
Модуль формы
#Область ОбработчикиСобытийФормы
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если НЕ Параметры.Свойство("ДополнительнаяОбработкаСсылка") Тогда
ПутьКВнешнейОбработке = РеквизитФормыВЗначение("Объект").ИспользуемоеИмяФайла;
Иначе
ДополнительнаяОбработкаСсылка = Параметры.ДополнительнаяОбработкаСсылка;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Если ЗначениеЗаполнено(ПутьКВнешнейОбработке) Тогда
АдресВнешнейОбработки = ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(ПутьКВнешнейОбработке), УникальныйИдентификатор);
ЗаписатьКопиюОбработки(АдресВнешнейОбработки, ПутьКопииОбработки);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПриЗакрытии(ЗавершениеРаботы)
УдалитьКопиюОбработкиНаСервере(ПутьКопииОбработки);
КонецПроцедуры
#КонецОбласти
#Область ОбработчикиКомандФормы
&НаКлиенте
Процедура Обзор(Команда)
ОписаниеОповещения = Новый ОписаниеОповещения("ОбзорПослеПомещенияФайла", ЭтотОбъект);
ПараметрыЗагрузки = ФайловаяСистемаКлиент.ПараметрыЗагрузкиФайла();
ПараметрыЗагрузки.ИдентификаторФормы = УникальныйИдентификатор;
ПараметрыЗагрузки.Диалог.Фильтр = "Табличный документ (*.xlsx *.xls)|*.xlsx;*.xls";
ФайловаяСистемаКлиент.ЗагрузитьФайл(ОписаниеОповещения, ПараметрыЗагрузки);
КонецПроцедуры
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
&НаКлиенте
Процедура ОбзорПослеПомещенияФайла(ПомещенныйФайл, ДополнительныеПараметры) Экспорт
Если Не ЗначениеЗаполнено(ПомещенныйФайл) Тогда
Возврат;
КонецЕсли;
ДлительнаяОперация = ЗагрузитьСтатусыНаСервере(ПомещенныйФайл.Хранение);
ОповещениеОЗавершении = Новый ОписаниеОповещения("ОбзорПослеПомещенияФайлаЗавершение", ЭтотОбъект);
ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
ПараметрыОжидания.Вставить("ВыводитьПрогрессВыполнения", Истина);
ПараметрыОжидания.Вставить("Интервал", 2);
ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, ОповещениеОЗавершении, ПараметрыОжидания);
КонецПроцедуры // ОбзорПослеПомещенияФайла()
&НаСервере
Функция ЗагрузитьСтатусыНаСервере(АдресПомещенногоФайла)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресПомещенногоФайла);
ОбъектОбработки = РеквизитФормыВЗначение("Объект");
МетаданныеОбработки = ОбъектОбработки.Метаданные();
ПараметрыДлительнойОперации = Новый Структура;
ПараметрыДлительнойОперации.Вставить("ДвоичныеДанные", ДвоичныеДанные);
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФункции(УникальныйИдентификатор);
ПараметрыВыполнения.НаименованиеФоновогоЗадания = МетаданныеОбработки.Синоним;
ПараметрыВыполнения.Вставить("ДополнительныйРезультат", Истина);
ПараметрыВыполнения.Удалить("АдресРезультата");
Если Отладка Тогда
ПараметрыВыполнения.Вставить("ЗапуститьНеВФоне", Истина);
КонецЕсли;
ПараметрыЗадания = Новый Структура;
ПараметрыЗадания.Вставить("ИмяМетода", "ЗагрузитьДанные");
ПараметрыЗадания.Вставить("ПараметрыВыполнения", ПараметрыДлительнойОперации);
ПараметрыЗадания.Вставить("ЭтоВнешняяОбработка", Истина);
Если ЗначениеЗаполнено(ДополнительнаяОбработкаСсылка) Тогда
ПараметрыЗадания.Вставить("ДополнительнаяОбработкаСсылка", ДополнительнаяОбработкаСсылка);
Иначе
ПараметрыЗадания.Вставить("ИмяОбработки", ПутьКопииОбработки);
КонецЕсли;
Возврат ДлительныеОперации.ВыполнитьПроцедуру(ПараметрыВыполнения, "ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки", ПараметрыЗадания);
КонецФункции
&НаКлиенте
Процедура ОбзорПослеПомещенияФайлаЗавершение(Результат, ДополнительныеПараметры) Экспорт
Если ЗначениеЗаполнено(Результат) Тогда
Если ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(Результат, "Статус") = "Выполнено" Тогда
РезультатСтруктура = ПолучитьИзВременногоХранилища(Результат.АдресДополнительногоРезультата);
ТекстСообщения = СтрШаблон("Всего обработано строк: %1", РезультатСтруктура.ВсегоСтрок);
Иначе
ТекстСообщения = ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(Результат, "ПодробноеПредставлениеОшибки", "Неизвестная ошибка");
КонецЕсли;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = ТекстСообщения;
Сообщение.Сообщить();
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗаписатьКопиюОбработки(АдресВнешнейОбработки, ПутьКопииОбработки)
ДвоичныеДанныеОбработки = ПолучитьИзВременногоХранилища(АдресВнешнейОбработки);
ПутьКопииОбработки = ПолучитьИмяВременногоФайла("epf");
ДвоичныеДанныеОбработки.Записать(ПутьКопииОбработки);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура УдалитьКопиюОбработкиНаСервере(ПутьКопииОбработки)
Если Не ЗначениеЗаполнено(ПутьКопииОбработки) Тогда
Возврат;
КонецЕсли;
ФайлОбработки = Новый Файл(ПутьКопииОбработки);
Если ФайлОбработки.Существует() Тогда
УдалитьФайлы(ПутьКопииОбработки);
КонецЕсли;
КонецПроцедуры // УдалитьКопиюОбработкиНаСервере(ПутьКопииОбработки)()
#КонецОбласти
При открытии формы происходит анализ места запуска и либо обработка сохраняется на сервере во временный файл, либо запоминается ссылка на справочник дополнительных обработок. В процедуре ОбзорПослеПомещенияФайла происходит вызов создания длительной операции и формирование параметров ожидания на клиенте.
В функции ЗагрузитьСтатусыНаСервере структура ПараметрыДлительнойОперации определяет параметры для основной процедуры из модуля объекта, в примере в параметрах передается полученный с клиента файл Excel в виде двоичных данных. Ключ ДополнительныйРезультат вместо АдресРезультата используется из-за безусловного алгоритма разработчиков БСП в вызове ДлительныеОперации.ВыполнитьПроцедуру.
Реквизит Отладка необходим для запуска алгоритма не в фоне для быстрой отладки на клиент-серверных базах без необходимости добавлять параметр запуска РежимОтладки в конфигураторе. На рабочей задаче необходимо скрыть данный реквизит на форме.
Именно процедура ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки позволяет запускать процедуры из модуля обработки. В зависимости от места запуска мы передаем либо ссылку, либо путь к временному файлу, куда скопирована обработка ранее. После завершения обработки в процедуре ОбзорПослеПомещенияФайлаЗавершение мы обращаемся именно к ключу АдресДополнительногоРезультата.
Форма и реквизиты
Известные проблемы
В процессе разработки можно получить следующую ошибку:
Она появляется из-за запуска платформой внешней обработки не из файла на диске, а из кеша дополнительных обработок. Кейс ошибки: обновляется справочник дополнительных обработок из файла на диске, запускается форма из формы справочника, а затем в этом же сеансе открывается обработка из файла на диске напрямую. Подобная ситуация работает и в обратном случае.
Если же не запускать из обоих мест в одном сеансе, то ошибки не будет.