Как реализовать сопоставление и интерактивную замену существующих документов при загрузке из XML в 1С?

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

При разработке механизмов загрузки данных из внешних источников (XML, JSON, Excel) разработчики часто сталкиваются с типовой дилеммой: что делать, если входящий документ уже присутствует в информационной базе? Попытка решать этот вопрос «в лоб» — задавать интерактивный вопрос пользователю на клиенте по каждому найденному совпадению или удалять старый документ перед созданием нового — приводит к падению производительности, блокировкам интерфейса и нарушению ссылочной целостности базы данных.

Разберем по шагам, как построить правильную архитектуру загрузки, избежать дублирования кода между клиентом и сервером и корректно перезаписывать объекты.

Типичные ошибки при реализации сопоставления документов

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

  1. Удаление старого документа с созданием нового. Если физически удалить найденный документ или пометить его на удаление, а затем создать новый экземпляр, у нового объекта сформируется другой УникальныйИдентификатор (GUID). В результате все внешние ссылки на старый документ (в подчиненных документах, регистрах сведений, движениях регистров накопления и бухгалтерии) превратятся в «битые» ссылки («Объект не найден»). Правильный подход — обновлять существующий объект по ссылке.
  2. Поиск документа только по номеру. Номер документа в 1С редко является глобально уникальным. В большинстве конфигураций настроена периодичность нумерации (в пределах года или месяца), а также используются префиксы организаций и узлов распределенных баз. Поиск только по номеру гарантированно приведет к случайной перезаписи документов прошлых периодов или чужих организаций.
  3. Интерактивные вопросы в цикле на клиенте. В современных конфигурациях на платформе 1С:Предприятие 8.3 включен запрет модальности (параметр РежимИспользованияМодальности = НеИспользовать). Модальные диалоги заблокированы, а вызов асинхронного метода ПоказатьВопрос внутри стандартного цикла невозможен без создания сложной цепочки рекурсивных методов через ОписаниеОповещения. Кроме того, опрос по каждому документу сильно утомляет пользователя при импорте сотен позиций.
  4. Использование массивов структур вместо таблиц значений. Массив структур на сервере сложно связать с запросом к базе данных. Чтобы пакетно проверить наличие документов, приходится либо обходить массив в цикле с выполнением запросов на каждой итерации (что крайне неэффективно), либо вручную собирать коллекции параметров.

Шаг 1. Переход на таблицу значений и пакетный поиск дублей

Рассмотрим, как оптимизировать парсинг и проверку наличия данных. Вместо массива структур сформируем на сервере объект ТаблицаЗначений. В ней зафиксируем все ключевые поля для поиска: номер, дату (или период), организацию, а также уникальный идентификатор из внешнего файла (если он передается в XML).

Затем передадим эту таблицу в качестве параметра в запрос. Это позволит за одно обращение к СУБД найти все существующие документы:


// В серверном контексте формируем типизированную таблицу загрузки
ТаблицаЗагрузки = Новый ТаблицаЗначений;
ТаблицаЗагрузки.Колонки.Добавить("Номер", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(30)));
ТаблицаЗагрузки.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));
ТаблицаЗагрузки.Колонки.Добавить("Организация", Новый ОписаниеТипов("СправочникСсылка.Организации"));
ТаблицаЗагрузки.Колонки.Добавить("ИдентификаторXML", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(50)));

// После заполнения таблицы из XML передаем ее в запрос
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Вход.Номер КАК Номер,
    |	Вход.Дата КАК Дата,
    |	Вход.Организация КАК Организация,
    |	Вход.ИдентификаторXML КАК ИдентификаторXML
    |ПОМЕСТИТЬ ВТ_Загрузка
    |ИЗ
    |	&ТаблицаЗагрузки КАК Вход
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ.Номер КАК Номер,
    |	ВТ.Дата КАК Дата,
    |	ВТ.Организация КАК Организация,
    |	ВТ.ИдентификаторXML КАК ИдентификаторXML,
    |	Реализация.Ссылка КАК НайденныйДокумент,
    |	ВЫБОР
    |		КОГДА Реализация.Ссылка ЕСТЬ NULL ТОГДА ЛОЖЬ
    |		ИНАЧЕ ИСТИНА
    |	КОНЕЦ КАК ЭтоДубль
    |ИЗ
    |	ВТ_Загрузка КАК ВТ
    |		ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
    |		ПО ВТ.Номер = Реализация.Номер
    |			И ВТ.Организация = Реализация.Организация
    |			И НАЧАЛОПЕРИОДА(ВТ.Дата, ГОД) = НАЧАЛОПЕРИОДА(Реализация.Дата, ГОД)";

Запрос.УстановитьПараметр("ТаблицаЗагрузки", ТаблицаЗагрузки);
РезультатПроверки = Запрос.Выполнить().Выгрузить();

Шаг 2. Организация пользовательского интерфейса через форму сопоставления

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

Разберем рекомендуемый порядок работы интерфейса:

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

Шаг 3. Корректная перезапись документов без поломки ссылок

Посмотрим, как выполнить саму загрузку и избежать создания дублей или повреждения связанных данных. Если пользователь выбрал вариант перезаписи, мы получаем объект существующего документа по найденной ссылке через Ссылка.ПолучитьОбъект(). Если документ новый — создаем его через метод СоздатьДокумент() менеджера документов.

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


// В серверной процедуре обработки
Для Каждого Стр Из ТаблицаРешений Цикл
    Если Стр.ЭтоДубль И Не Стр.Перезаписать Тогда
        // Пользователь отказался обновлять документ - пропускаем
        Продолжить;
    КонецЕсли;

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

    // Включаем режим загрузки для отключения прикладных проверок
    ДокументОбъект.ОбменДанными.Загрузка = Истина;
    
    // Заполняем реквизиты документа из ранее распарсенных данных
    ДокументОбъект.Номер = Стр.Номер;
    ДокументОбъект.Дата = Стр.Дата;
    ДокументОбъект.Организация = Стр.Организация;
    
    // Заполняем табличные части...
    
    ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЦикла;

Параллельная работа и блокировки данных

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

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

Резюме

Отказ от модальных вопросов в цикле в пользу единого табличного интерфейса сопоставления снимает проблему дублирования кода между клиентом и сервером. Использование метода ПолучитьОбъект() вместо связки «удаление + создание нового» гарантирует целостность базы данных и сохраняет внутренние связи конфигурации.

← На главную