Как в Конвертации данных 2.1 разделить один загружаемый документ на несколько на стороне базы-приемника?

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

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

В этой статье мы подробно рассмотрим, как решить эту задачу в «Конвертации данных 2.1». Мы разберем, почему стандартные подходы вызывают ошибки, и проанализируем три метода реализации: от простого скрипта в обработчике до высокопроизводительной пакетной загрузки.

Почему не работают стандартные механизмы в ПВД и «Перед загрузкой»?

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

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

Единственное место, где мы имеем одновременно и загруженные данные (подразделение, период, сумма), и доступ к базе-приемнику — это обработчики ПКО после инициализации объекта.

Способ 1. Программное создание документов в обработчике «После загрузки» ПКО

Это наиболее очевидный способ, который часто применяется для быстрого решения задачи. Логика работы следующая: мы дожидаемся, пока КД считает данные из XML и заполнит ими объект в памяти. Затем, используя эти данные, мы делаем запрос к базе-приемнику, находим сотрудников, программно создаем новые документы, а исходный загружаемый документ отменяем.

Рассмотрим по шагам реализацию этого алгоритма в обработчике «После загрузки» Правила Конвертации Объектов (ПКО):


// 1. Проверяем, что объект заполнен
Если НЕ ЗначениеЗаполнено(Объект.Подразделение) Тогда
    Возврат;
КонецЕсли;

// 2. Запросом получаем сотрудников подразделения на дату документа из базы ЗУП
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	КадроваяИсторияСрезПоследних.Сотрудник КАК Сотрудник
    |ИЗ
    |	РегистрСведений.КадроваяИсторияСотрудников.СрезПоследних(&Период, Подразделение = &Подразделение) КАК КадроваяИсторияСрезПоследних";

Запрос.УстановитьПараметр("Период", Объект.Дата);
Запрос.УстановитьПараметр("Подразделение", Объект.Подразделение);

ТаблицаСотрудников = Запрос.Выполнить().Выгрузить();

Если ТаблицаСотрудников.Количество() = 0 Тогда
    // Если сотрудников нет, документ по этому подразделению нам не нужен
    Отказ = Истина;
    Возврат;
КонецЕсли;

// 3. Создаем документы для каждого найденного сотрудника
Для Каждого СтрокаСотрудник Из ТаблицаСотрудников Цикл
    НовыйДок = Документы.ДанныеДляРасчетаЗарплаты.СоздатьДокумент();
    ЗаполнитьЗначенияСвойств(НовыйДок, Объект, "Дата, Организация, Подразделение, СпособВводаДанных");
    
    // Заполняем специфичные реквизиты
    НовыйДок.Сотрудник = СтрокаСотрудник.Сотрудник;
    НовыйДок.РазмерВыручки = Объект.РазмерВыручки; // Переносим сумму
    
    // Записываем документ программно
    Попытка
        НовыйДок.Записать(РежимЗаписиДокумента.Проведение);
    Исключение
        НовыйДок.Записать(РежимЗаписиДокумента.Запись);
        ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Предупреждение, , , 
            "Не удалось провести документ для сотрудника: " + СтрокаСотрудник.Сотрудник + ". Ошибка: " + ОписаниеОшибки());
    КонецПопытки;
КонецЦикла;

// 4. Отклоняем запись исходного документа, который загружала КД
Отказ = Истина;

Важное предупреждение: У данного способа есть два существенных недостатка. Во-первых, если на исходный документ в XML-файле были ссылки из других объектов (например, из связанных документов или регистров), то установка Отказ = Истина приведет к появлению «битых» ссылок (объектов типа <Объект не найден>). Во-вторых, при повторном обмене этот код будет плодить дубли документов, так как КД не сопоставляет программно созданные объекты.

Способ 2. Оптимизированный подход с модификацией текущего объекта

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

Проанализируем измененную логику в обработчике «После загрузки» ПКО:


// Получаем список сотрудников (аналогично Способу 1)
// ... (код запроса опущен для краткости) ...

Если ТаблицаСотрудников.Количество() = 0 Тогда
    Отказ = Истина;
    Возврат;
КонецЕсли;

// Для первого сотрудника используем сам загружаемый объект
ПервыйСотрудник = ТаблицаСотрудников[0].Сотрудник;
Объект.Сотрудник = ПервыйСотрудник;
// Объект будет записан штатно средствами КД, так как мы НЕ ставим Отказ = Истина

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

Этот метод значительно безопаснее. Ссылка на исходный документ сохраняется в базе данных, а значит, внешние связи не разрушатся. Однако проблема производительности при больших объемах данных (проблема N+1 запросов в цикле загрузки) все еще остается актуальной.

Способ 3. Пакетная загрузка через «После загрузки данных» (Профессиональное решение)

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

Разберем пошагово, как реализовать пакетную загрузку.

Шаг 1. Инициализация таблицы накопления

В глобальном обработчике правил конвертации «Перед загрузкой данных» создадим временную таблицу в параметрах обмена:


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

Параметры.Вставить("ТаблицаВыручкиИсточника", ТаблицаВыручки);

Шаг 2. Накопление данных в ПКО

В обработчике ПКО «После загрузки» мы не делаем никаких запросов к СУБД. Мы просто переносим данные в нашу таблицу и отказываемся от записи пустого документа-пустышки:


НоваяСтрока = Параметры.ТаблицаВыручкиИсточника.Добавить();
НоваяСтрока.Период = Объект.Дата;
НоваяСтрока.Подразделение = Объект.Подразделение;
НоваяСтрока.Организация = Объект.Организация;
НоваяСтрока.Сумма = Объект.РазмерВыручки;

// Отменяем запись единичного документа
Отказ = Истина;

Шаг 3. Единая пакетная обработка данных

В глобальном обработчике правил «После загрузки данных» пишем один оптимизированный алгоритм, который соединит всю таблицу накопления с кадровой историей за один запрос и создаст итоговые документы:


ТаблицаДанных = Параметры.ТаблицаВыручкиИсточника;
Если ТаблицаДанных.Количество() = 0 Тогда
    Возврат;
КонецЕсли;

// Передаем таблицу в запрос как параметр
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВремТаблица.Период КАК Период,
    |	ВремТаблица.Подразделение КАК Подразделение,
    |	ВремТаблица.Организация КАК Организация,
    |	ВремТаблица.Сумма КАК Сумма
    |ПОМЕСТИТЬ ВТ_ИсходныеДанные
    |ИЗ
    |	&ТаблицаДанных КАК ВремТаблица
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_ИсходныеДанные.Период КАК Период,
    |	ВТ_ИсходныеДанные.Подразделение КАК Подразделение,
    |	ВТ_ИсходныеДанные.Организация КАК Организация,
    |	ВТ_ИсходныеДанные.Сумма КАК Сумма,
    |	КадроваяИстория.Сотрудник КАК Сотрудник
    |ИЗ
    |	ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КадроваяИсторияСотрудников КАК КадроваяИстория
    |		ПО ВТ_ИсходныеДанные.Подразделение = КадроваяИстория.Подразделение
    |			И КадроваяИстория.Период <= ВТ_ИсходныеДанные.Период";

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

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

Преимущества пакетного метода:

Выбор конкретного метода зависит от объемов переносимой информации. Для редких разовых обменов с небольшим объемом данных отлично подойдет Способ 2. Если же планируется регулярный обмен большими объемами документов, настоятельно рекомендуем использовать Способ 3.

← На главную