При разработке обработок загрузки данных из внешних источников (например, файлов Excel) программисты часто сталкиваются с классической архитектурной проблемой платформы «1С:Предприятие 8.3». Нам необходимо прочитать файл, найти номенклатуру по штрихкоду в базе данных, сопоставить наименования и, если они отличаются, спросить пользователя: «Переименовать этот товар в базе на новое наименование из Excel?».
Попытка вызвать интерактивный диалог или открыть форму непосредственно из серверного контекста приводит к ошибке выполнения. Давайте подробно разберем, почему сервер не может напрямую взаимодействовать с пользователем, как обойти это ограничение, правильно спроектировать интерфейс и решить сопутствующие технические проблемы, такие как динамическое создание колонок таблицы формы и оптимизация длительных операций.
Для начала проанализируем ситуацию и выясним причину главного ограничения платформы. В управляемом приложении 1С действует жесткое разделение на клиентский и серверный контексты:
Именно поэтому невозможно вызвать функцию опроса пользователя из серверного модуля. Решать эту задачу нужно путем разделения процесса на четкие этапы и организации двустороннего обмена данными.
Вместо того чтобы пытаться задавать вопросы во время выполнения серверного кода, мы реализуем двухэтапную схему работы, предложенную участниками форума:
Рассмотрим по шагам техническую реализацию этой схемы.
В сообщении №7 участница форума пожаловалась, что у нее «не получилось с таблицей», так как она пыталась динамически задать колонки таблицы по шапке из файла Excel. Это распространенное затруднение.
Объект ТаблицаЗначений является исключительно серверным типом данных. Его невозможно передать на клиент в чистом виде. Чтобы отобразить динамическую таблицу на управляемой форме, необходимо использовать реквизиты формы типа ДанныеФормыКоллекция и программно добавлять элементы интерфейса.
Давайте разберем пример кода, который динамически создает колонки на форме под структуру импортируемого файла на сервере:
&НаСервере
Процедура СоздатьДинамическуюТаблицуНаФорме(СтруктураКолонок)
// Шаг 1. Подготовим массив добавляемых реквизитов формы
ДобавляемыеРеквизиты = Новый Массив;
Для Каждого ИмяКолонки Из СтруктураКолонок Цикл
// Добавляем реквизиты внутрь существующей таблицы на форме "ТаблицаИмпорта"
НовыйРеквизит = Новый РеквизитФормы(ИмяКолонки, Новый ОписаниеТипов("Строка"), "ТаблицаИмпорта", ИмяКолонки);
ДобавляемыеРеквизиты.Добавить(НовыйРеквизит);
КонецЦикла;
// Добавим системный флаг для выбора пользователя
РеквизитФлаг = Новый РеквизитФормы("ВыполнитьЗамену", Новый ОписаниеТипов("Булево"), "ТаблицаИмпорта", "Заменить?");
ДобавляемыеРеквизиты.Добавить(РеквизитФлаг);
// Применяем изменения к форме
ИзменитьРеквизиты(ДобавляемыеРеквизиты);
// Шаг 2. Создаем элементы управления на форме (визуальные колонки)
Для Каждого ИмяКолонки Из СтруктураКолонок Цикл
ИмяЭлемента = "ТаблицаИмпорта" + ИмяКолонки;
НовыйЭлемент = Элементы.Добавить(ИмяЭлемента, Тип("ПолеФормы"), Элементы.ТаблицаИмпорта);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.ПутьКДанным = "ТаблицаИмпорта." + ИмяКолонки;
КонецЦикла;
// Визуализируем колонку с флажком
ЭлементФлаг = Элементы.Добавить("ТаблицаИмпортаФлаг", Тип("ПолеФормы"), Элементы.ТаблицаИмпорта);
ЭлементФлаг.Вид = ВидПоляФормы.ПолеФлажка;
ЭлементФлаг.ПутьКДанным = "ТаблицаИмпорта.ВыполнитьЗамену";
КонецПроцедуры
Если структура файла Excel стабильна и известна заранее, динамическое создание реквизитов не требуется. В этом случае достаточно заранее вручную создать таблицу на форме и наполнять ее данными.
Если в процессе импорта нам всё же требуется задать пользователю вопрос (например, перед началом долгой перезаписи), мы обязаны учитывать, что в современных конфигурациях включен режим «Запретить использование модальных окон». Попытка вызвать старый метод Вопрос() приведет к аварийному завершению программы.
Для платформ версии 8.3.18 и выше рекомендуется использовать современный и лаконичный механизм асинхронности с операторами Асинх и Ждать. Это позволяет писать линейный и понятный код без разбиения логики на разрозненные процедуры-колбэки.
Посмотрим на пример реализации асинхронного вопроса на клиенте:
&НаКлиенте
Асинх Процедура ВыполнитьИмпортДанных()
// Перед началом записи зададим асинхронный вопрос пользователю
РежимДиалога = РежимДиалогаВопрос.ДаНет;
ТекстВопроса = "Обнаружены расхождения в наименованиях товаров. Желаете обновить их в базе?";
Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалога, 15, КодВозвратаДиалога.Нет, "Внимание!");
Если Ответ = КодВозвратаДиалога.Да Тогда
ПоказатьОповещениеПользователя("Запуск процесса",, "Обновление справочника номенклатуры...");
ВыполнитьЗаписьНаСервере();
Иначе
ПоказатьПредупреждениеАсинх("Запись отменена пользователем.");
КонецЕсли;
КонецПроцедуры
Чтение файлов Excel объемом в тысячи строк и их сопоставление с базой данных по штрихкоду — ресурсоемкий процесс. Если запустить его синхронно в обработчике кнопки, интерфейс пользователя зависнет (операционная система покажет статус «Не отвечает»), а веб-клиент может оборвать соединение из-за таймаута.
Правильным решением в промышленной разработке является использование механизма длительных операций из Библиотеки стандартных подсистем (БСП). Рассмотрим этот паттерн:
ПоместитьФайлНаСервер().ДлительныеОперации.ЗапуститьВыполнениеНаСервере(). Файл разбирается в фоновом потоке, не блокируя работу пользователя.Давайте посмотрим на пример программного кода для запуска фонового задания и получения результатов через БСП:
&НаКлиенте
Процедура НачатьАнализФайла()
// Сначала передаем файл во временное хранилище
ОписаниеФайла = Новый ОписаниеПередаваемогоФайла(ПутьКФайлу);
ПередаваемыеФайлы = Новый Массив;
ПередаваемыеФайлы.Добавить(ОписаниеФайла);
// Помещаем файл на сервер
АдресФайлаВХранилище = "";
Если ПоместитьФайлНаСервер(,,, АдресФайлаВХранилище, УникальныйИдентификатор) Тогда
// Запускаем анализ в фоновом задании
ЗапуститьФоновыйАнализ(АдресФайлаВХранилище);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ЗапуститьФоновыйАнализ(АдресФайла)
ПараметрыПроцедуры = Новый Структура;
ПараметрыПроцедуры.Вставить("АдресФайла", АдресФайла);
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФоновогоЗадания(УникальныйИдентификатор);
ПараметрыВыполнения.НаименованиеЗадания = "Анализ расхождений импорта Excel";
// Запускаем фоновый метод общего модуля
Результат = ДлительныеОперации.ЗапуститьВыполнениеНаСервере("МойОбщийМодуль.ВыполнитьАнализФайла", ПараметрыПроцедуры, ПараметрыВыполнения);
Если Результат.Статус = "Выполняется" Тогда
АдресРезультата = Результат.АдресРезультата;
ИдентификаторЗадания = Результат.ИдентификаторЗадания;
КонецЕсли;
КонецПроцедуры
Важно помнить о времени жизни объектов во временном хранилище. При передаче таблицы расхождений между сервером, фоновым заданием и клиентом необходимо обязательно привязывать адрес хранилища к уникальному идентификатору формы (параметр УникальныйИдентификатор).
Если этого не сделать, то сразу после завершения серверного вызова или фонового задания платформа очистит память, и клиент получит ошибку чтения по адресу временного хранилища. Привязка к идентификатору формы гарантирует сохранность данных на сервере до момента закрытия этой формы пользователем.
Пример корректного сохранения таблицы расхождений во временном хранилище на сервере:
&НаСервере
Процедура СохранитьТаблицуВХранилище(ТаблицаРасхождений)
// Привязываем время жизни данных к текущей форме
АдресТаблицыРасхождений = ПоместитьВоВременноеХранилище(ТаблицаРасхождений, УникальныйИдентификатор);
КонецПроцедуры
Для создания надежного и дружелюбного интерфейса импорта данных всегда разделяйте процесс на три этапа: подготовка данных на сервере в фоновом режиме, интерактивная работа пользователя со списком изменений на клиенте (с использованием асинхронных методов 1С) и последующая транзакционная запись одобренных изменений в базу данных.