Как задать пользователю вопрос с сервера при импорте данных из Excel в 1С?

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

При разработке обработок загрузки данных из внешних источников (например, файлов Excel) программисты часто сталкиваются с классической архитектурной проблемой платформы «1С:Предприятие 8.3». Нам необходимо прочитать файл, найти номенклатуру по штрихкоду в базе данных, сопоставить наименования и, если они отличаются, спросить пользователя: «Переименовать этот товар в базе на новое наименование из Excel?».

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

Почему сервер «игнорирует» клиента: основы архитектуры 1С

Для начала проанализируем ситуацию и выясним причину главного ограничения платформы. В управляемом приложении 1С действует жесткое разделение на клиентский и серверный контексты:

  1. Клиент (Тонкий клиент, Веб-клиент): отвечает за отображение интерфейса, взаимодействие с пользователем, получение файлов и обработку простых событий. Здесь доступны модальные или асинхронные вопросы, открытие форм и предупреждения. При этом у клиента нет прямого доступа к базе данных.
  2. Сервер: обладает полным доступом к базе данных, механизмам запросов и объектной технике (создание, изменение, запись элементов). Однако сервер абсолютно «слеп» по отношению к интерфейсу конкретного пользователя. Он не знает, на каком устройстве запущена программа, и не имеет права прерывать свою работу для ожидания ответа от пользователя.

Именно поэтому невозможно вызвать функцию опроса пользователя из серверного модуля. Решать эту задачу нужно путем разделения процесса на четкие этапы и организации двустороннего обмена данными.

Концепция двухэтапного импорта

Вместо того чтобы пытаться задавать вопросы во время выполнения серверного кода, мы реализуем двухэтапную схему работы, предложенную участниками форума:

  1. Этап 1 (Чтение и анализ): Читаем файл Excel, передаем данные на сервер, выполняем поиск в базе данных и сопоставляем штрихкоды и наименования. Все расхождения упаковываем в специальную таблицу и возвращаем ее на клиент.
  2. Этап 2 (Интерактивное одобрение): Показываем пользователю красивую таблицу со списком всех расхождений. Пользователь отмечает флажками («галочками») те строки, по которым он согласен выполнить изменения, и нажимает кнопку «Записать». Только после этого измененные объекты записываются в базу данных.

Рассмотрим по шагам техническую реализацию этой схемы.

Решаем проблему динамического создания колонок на форме

В сообщении №7 участница форума пожаловалась, что у нее «не получилось с таблицей», так как она пыталась динамически задать колонки таблицы по шапке из файла Excel. Это распространенное затруднение.

Объект ТаблицаЗначений является исключительно серверным типом данных. Его невозможно передать на клиент в чистом виде. Чтобы отобразить динамическую таблицу на управляемой форме, необходимо использовать реквизиты формы типа ДанныеФормыКоллекция и программно добавлять элементы интерфейса.

Давайте разберем пример кода, который динамически создает колонки на форме под структуру импортируемого файла на сервере:


&НаСервере
Процедура СоздатьДинамическуюТаблицуНаФорме(СтруктураКолонок)

    // Шаг 1. Подготовим массив добавляемых реквизитов формы
    ДобавляемыеРеквизиты = Новый Массив;
    
    Для Каждого ИмяКолонки Из СтруктураКолонок Цикл
        // Добавляем реквизиты внутрь существующей таблицы на форме "ТаблицаИмпорта"
        НовыйРеквизит = Новый РеквизитФормы(ИмяКолонки, Новый ОписаниеТипов("Строка"), "ТаблицаИмпорта", ИмяКолонки);
        ДобавляемыеРеквизиты.Добавить(НовыйРеквизит);
    КонецЦикла;
    
    // Добавим системный флаг для выбора пользователя
    РеквизитФлаг = Новый РеквизитФормы("ВыполнитьЗамену", Новый ОписаниеТипов("Булево"), "ТаблицаИмпорта", "Заменить?");
    ДобавляемыеРеквизиты.Добавить(РеквизитФлаг);
    
    // Применяем изменения к форме
    ИзменитьРеквизиты(ДобавляемыеРеквизиты);
    
    // Шаг 2. Создаем элементы управления на форме (визуальные колонки)
    Для Каждого ИмяКолонки Из СтруктураКолонок Цикл
        ИмяЭлемента = "ТаблицаИмпорта" + ИмяКолонки;
        НовыйЭлемент = Элементы.Добавить(ИмяЭлемента, Тип("ПолеФормы"), Элементы.ТаблицаИмпорта);
        НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
        НовыйЭлемент.ПутьКДанным = "ТаблицаИмпорта." + ИмяКолонки;
    КонецЦикла;
    
    // Визуализируем колонку с флажком
    ЭлементФлаг = Элементы.Добавить("ТаблицаИмпортаФлаг", Тип("ПолеФормы"), Элементы.ТаблицаИмпорта);
    ЭлементФлаг.Вид = ВидПоляФормы.ПолеФлажка;
    ЭлементФлаг.ПутьКДанным = "ТаблицаИмпорта.ВыполнитьЗамену";

КонецПроцедуры

Если структура файла Excel стабильна и известна заранее, динамическое создание реквизитов не требуется. В этом случае достаточно заранее вручную создать таблицу на форме и наполнять ее данными.

Отказ от модальности и современный асинхронный диалог

Если в процессе импорта нам всё же требуется задать пользователю вопрос (например, перед началом долгой перезаписи), мы обязаны учитывать, что в современных конфигурациях включен режим «Запретить использование модальных окон». Попытка вызвать старый метод Вопрос() приведет к аварийному завершению программы.

Для платформ версии 8.3.18 и выше рекомендуется использовать современный и лаконичный механизм асинхронности с операторами Асинх и Ждать. Это позволяет писать линейный и понятный код без разбиения логики на разрозненные процедуры-колбэки.

Посмотрим на пример реализации асинхронного вопроса на клиенте:


&НаКлиенте
Асинх Процедура ВыполнитьИмпортДанных()

    // Перед началом записи зададим асинхронный вопрос пользователю
    РежимДиалога = РежимДиалогаВопрос.ДаНет;
    ТекстВопроса = "Обнаружены расхождения в наименованиях товаров. Желаете обновить их в базе?";
    
    Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалога, 15, КодВозвратаДиалога.Нет, "Внимание!");
    
    Если Ответ = КодВозвратаДиалога.Да Тогда
        ПоказатьОповещениеПользователя("Запуск процесса",, "Обновление справочника номенклатуры...");
        ВыполнитьЗаписьНаСервере();
    Иначе
        ПоказатьПредупреждениеАсинх("Запись отменена пользователем.");
    КонецЕсли;

КонецПроцедуры

Использование механизма «Длительные операции» (БСП)

Чтение файлов Excel объемом в тысячи строк и их сопоставление с базой данных по штрихкоду — ресурсоемкий процесс. Если запустить его синхронно в обработчике кнопки, интерфейс пользователя зависнет (операционная система покажет статус «Не отвечает»), а веб-клиент может оборвать соединение из-за таймаута.

Правильным решением в промышленной разработке является использование механизма длительных операций из Библиотеки стандартных подсистем (БСП). Рассмотрим этот паттерн:

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

Давайте посмотрим на пример программного кода для запуска фонового задания и получения результатов через БСП:


&НаКлиенте
Процедура НачатьАнализФайла()

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

КонецПроцедуры

&НаСервере
Процедура ЗапуститьФоновыйАнализ(АдресФайла)

    ПараметрыПроцедуры = Новый Структура;
    ПараметрыПроцедуры.Вставить("АдресФайла", АдресФайла);
    
    ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФоновогоЗадания(УникальныйИдентификатор);
    ПараметрыВыполнения.НаименованиеЗадания = "Анализ расхождений импорта Excel";
    
    // Запускаем фоновый метод общего модуля
    Результат = ДлительныеОперации.ЗапуститьВыполнениеНаСервере("МойОбщийМодуль.ВыполнитьАнализФайла", ПараметрыПроцедуры, ПараметрыВыполнения);
    
    Если Результат.Статус = "Выполняется" Тогда
        АдресРезультата = Результат.АдресРезультата;
        ИдентификаторЗадания = Результат.ИдентификаторЗадания;
    КонецЕсли;

КонецПроцедуры

Особенности работы с Временным хранилищем

Важно помнить о времени жизни объектов во временном хранилище. При передаче таблицы расхождений между сервером, фоновым заданием и клиентом необходимо обязательно привязывать адрес хранилища к уникальному идентификатору формы (параметр УникальныйИдентификатор).

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

Пример корректного сохранения таблицы расхождений во временном хранилище на сервере:


&НаСервере
Процедура СохранитьТаблицуВХранилище(ТаблицаРасхождений)

    // Привязываем время жизни данных к текущей форме
    АдресТаблицыРасхождений = ПоместитьВоВременноеХранилище(ТаблицаРасхождений, УникальныйИдентификатор);

КонецПроцедуры

Резюме

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

← На главную