Как передать Excel с клиента в типовой отчёт БСП и ничего не сломать
Постановка
Есть внешний отчёт, которому нужны данные из файлов Excel на компьютере пользователя. Читать файлы следует на клиенте - клиент у нас на Windows, - а обрабатывать данные, обращаться к справочникам и формировать результат нужно на сервере. Сервер уже как повезёт: Windows ему никто не обещал.
При этом хочется пользоваться всеми прелестями типовой формы отчёта БСП: вариантами, настройками, отборами, расшифровками, фоновым формированием и привычным интерфейсом.
Конечно, мы не варвары, чтобы копировать типовую форму или ломать её. Поэтому воспользуемся возможностями, которые нам любезно предоставила БСП.
Подключаемся к типовой форме
В регистрационных сведениях внешнего отчёта устанавливаем:
Параметры.ОпределитьНастройкиФормы = Истина;
После этого БСП вызывает экспортные обработчики отчёта. Через ОпределитьНастройкиФормы() и ПриСозданииФормы() можно добавить собственную команду в стандартную форму, не копируя её.
Наша команда предлагает выбрать каталог, читает Excel на клиенте и записывает подготовленные данные в параметр СКД. Дальше пользователь нажимает обычную кнопку «Сформировать», и отчёт продолжает жить по правилам БСП.
Почему не временное хранилище
Первой приходит очевидная мысль: вызвать ПоместитьВоВременноеХранилище() и передать в отчёт адрес. Но адрес временного хранилища нельзя надёжно передать в фоновое задание формирования отчёта: фоновое задание работает уже в другом контексте.
Здесь возникает интересный эффект — с такой реализацией отчёт работает только в режиме отладки! Пока формирование выполняется в текущем сеансе, данные находятся. Как только типовая форма отправляет отчёт в фон, адрес становится бесполезным.
Можно было бы передать ХранилищеЗначения, но, чтобы создать ХранилищеЗначения, нужен сервер. Значит, клиентский модуль должен сделать серверный вызов только ради упаковки уже прочитанных данных. Нам этого не хочется.
Вот так мы и пришли к параметру-строке.
Строка, сериализация и сжатие
Алгоритм на клиенте получается простым:
- Прочитать Excel.
- Собрать массив структур только из переносимых типов: строк, чисел и дат.
- Сериализовать массив в JSON.
- Сжать JSON в ZIP.
- Преобразовать архив в Base64.
- Записать полученную строку в скрытый параметр СКД.
Схематично это выглядит так:
СтрокаJSON = СериализоватьВJSON(Данные);
СжатыеДанные = СжатьСтрокуВBase64(СтрокаJSON);
УстановитьПараметрОтчета(ФормаОтчета, "ДанныеФайлов", СжатыеДанные);
Почему не передавать JSON без сжатия? Потому что табличные данные хорошо сжимаются: повторяются имена полей, структура строк и похожие значения. На наших примерах ZIP с последующим Base64 уменьшает передаваемую строку примерно в восемь раз по сравнению с исходным JSON.
Да, Base64 немного увеличивает размер архива, но результат всё равно значительно компактнее несжатого JSON.
Чтобы пользователь не нервничал

Excel читается на клиенте, причём чтение нескольких файлов через COM может занять заметное время. Оставлять пользователя перед неподвижной формой с вопросом «оно работает или уже умерло?» не стоит. Практика показывает, что через несколько минут этот вопрос всё равно зададут программисту.
Писать ради этого ещё одну форму прогресса было бы привычным решением: сначала создать проблему, затем окружить её собственным интерфейсом. К счастью, на этот раз можно остановиться раньше. В составе БСП уже есть общая форма длительной операции:
ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(Неопределено);
ФормаПрогресса = ПолучитьФорму(
"ОбщаяФорма.ДлительнаяОперация",
ПараметрыОжидания,
ФормаОтчета);
ФормаПрогресса.Открыть();
Перед чтением очередного файла обновляем поясняющий текст:
ФормаПрогресса.Элементы.ДекорацияПоясняющийТекстДлительнойОперации.Заголовок =
СтрШаблон("(%1 / %2) %3", НомерФайла, КоличествоФайлов, ИмяФайла);
Пользователь видит, какой файл сейчас читается и сколько их осталось. После завершения клиентской части форму прогресса закрываем, а обычное формирование отчёта дальше выполняет типовая форма БСП, в том числе в фоне.
Что происходит на сервере
В ПриКомпоновкеРезультата() отчёт получает строковый параметр, выполняет обратные действия и восстанавливает массив:
ДвоичныеДанныеАрхива = Base64Значение(СтрокаBase64);
// Распаковка ZIP во временный каталог.
СтрокаJSON = РаспаковатьСтроку(СтрокаBase64);
Данные = ПрочитатьJSON(ЧтениеJSON, Ложь, "Дата", ФорматДатыJSON.ISO);
После этого уже на сервере можно искать контрагентов по ИНН, номенклатуру по артикулу, выполнять запросы и передавать таблицы во внешние наборы данных СКД.
Клиентский модуль при этом не делает серверных вызовов. Он отвечает только за локальные файлы и упаковку данных. Сервер отвечает за базу и формирование отчёта.
Что получили

В результате:
- типовая форма отчёта осталась типовой;
- отчет формируется в фоне (кроме клиентской части, конечно);
- клиент читает Excel без серверных вызовов - пока мы на винде, в нашем распоряжении COM и весь виндовский инструментарий;
- сервер получает все исходные данные одним параметром;
- передаваемый объём уменьшается примерно в восемь раз;
- настройки, варианты, отборы и весь штатный арсенал БСП остаются с нами.
Редкий приятный результат: ещё один механизм не пришлось копировать, переписывать и затем годами сопровождать. Мы воспользовались штатными точками расширения БСП, и типовая форма осталась типовой.
PS. Почему не ТабличныйДокумент.Прочитать()?
Потому что исходные файлы были не такими простыми. Иначе этой статьи, к счастью, не понадобилось бы.
PPS. Расширение, отчёт и эта статья написаны целиком с помощью нейронки - с предоставлением ей оригинала, конечно. Самостоятельно догадываться о требованиях она пока умеет примерно как обычный разработчик.
PPPS. Панель разделов скрывается CTRL+SHIFT+~
Проверено на следующих конфигурациях и релизах:
- Управление торговлей, редакция 11, релизы 11.5.12.195
Вступайте в нашу телеграмм-группу Инфостарт