При необходимости перенести данные регистра накопления, например, НачисленияУдержанияПоСотрудникам из одной базы 1С:ЗУП в другую, часто возникает задача сохранения движений регистра без переноса исходных документов-регистраторов. Типовые механизмы обмена данными (Конвертация Данных) требуют, чтобы каждое движение регистра было привязано к регистратору. В таких случаях, создание служебного документа ПереносДанных в базе-приемнике для каждого набора движений является распространенным решением. Однако при такой реализации могут возникнуть ошибки и проблемы с дублированием данных.
В этой статье мы подробно рассмотрим, как правильно настроить выгрузку данных регистра накопления в связке с документом ПереносДанных, а также разберем типичные ошибки и способы их устранения, основываясь на практическом опыте.
Определение стратегии переноса.
Прежде всего, нам необходимо решить, как будут переноситься данные. Изначальная задача — выгрузить движения регистра НачисленияУдержанияПоСотрудникам, но не переносить документы, которые эти движения породили. Вместо оригинальных документов в базе-приемнике будет создаваться документ ПереносДанных, который выступит в роли регистратора для этих движений. При этом нам важно сохранить привязку к исходному документу для целей идентификации и предотвращения дублирования при повторных обменах.
Настройка запроса для выгрузки движений.
В правиле выгрузки данных (ПВД) для регистра накопления НачисленияУдержанияПоСотрудникам нам потребуется процедура ПередОбработкой, в которой будет сформирован запрос для получения всех необходимых полей регистра. Важным аспектом здесь является группировка по регистратору, так как для каждого исходного регистратора мы будем создавать отдельный документ ПереносДанных.
Запрос = Новый Запрос(
"ВЫБРАТЬ
| НачисленияУдержанияПоСотрудникам.Период КАК Период,
| НачисленияУдержанияПоСотрудникам.Регистратор КАК Регистратор,
| НачисленияУдержанияПоСотрудникам.НомерСтроки КАК НомерСтроки,
| НачисленияУдержанияПоСотрудникам.Активность КАК Активность,
| НачисленияУдержанияПоСотрудникам.Организация КАК Организация,
| НачисленияУдержанияПоСотрудникам.ФизическоеЛицо КАК ФизическоеЛицо,
| ... тут еще куча реквизитов
| НачисленияУдержанияПоСотрудникам.МестоПолученияДохода КАК МестоПолученияДохода,
| НачисленияУдержанияПоСотрудникам.ВидДоходаИсполнительногоПроизводства КАК ВидДоходаИсполнительногоПроизводства
|ИЗ
| РегистрНакопления.НачисленияУдержанияПоСотрудникам КАК НачисленияУдержанияПоСотрудникам
|ГДЕ
| НачисленияУдержанияПоСотрудникам.Период МЕЖДУ &ДатаНачала И &ДатаОкончания
|
|ИТОГИ ПО
| НачисленияУдержанияПоСотрудникам.Регистратор");
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаНачала));
Запрос.УстановитьПараметр("ДатаОкончания", КОнецДня(ДатаОкончания));
Таб = Новый ТаблицаЗначений();
Таб.Колонки.Добавить("Активность");
Таб.Колонки.Добавить("ВидДоходаИсполнительногоПроизводства");
Таб.Колонки.Добавить("ГруппаНачисленияУдержанияВыплаты");
Таб.Колонки.Добавить("ДанныеМежрасчетногоПериода");
// ... тут еще куча колонок
Таб.Колонки.Добавить("ФизическоеЛицо");
ВыборкаРег = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаРег.Следующий() Цикл
Выборка = ВыборкаРег.Выбрать();
Пока Выборка.Следующий() Цикл
Стр = Таб.Добавить();
ЗаполнитьЗначенияСвойств(Стр, Выборка);
КонецЦикла;
ТаблицаИменРегистров = Новый ТаблицаЗначений();
ТаблицаИменРегистров.Колонки.Добавить("Имя");
ТаблицаИменРегистров.Колонки.Добавить("Представление");
СтрокаТаблицы = ТаблицаИменРегистров.Добавить();
СтрокаТаблицы.Имя = "НачисленияУдержанияПоСотрудникам";
СтрокаТаблицы.Представление = "Начисления удержания по сотрудникам";
Регистратор = ВыборкаРег.Регистратор;
ИсходящиеДанные = Новый Структура;
ИсходящиеДанные.Вставить("Номер", Регистратор.Номер);
ИсходящиеДанные.Вставить("Дата", Регистратор.Дата);
ИсходящиеДанные.Вставить("ПериодРегистрации", Регистратор.Дата);
ИсходящиеДанные.Вставить("Организация", Регистратор.Организация);
ИсходящиеДанные.Вставить("ТаблицаРегистровНакопления", ТаблицаИменРегистров);
ИсходящиеДанные.Вставить("НачисленияУдержанияПоСотрудникам", Таб);
ВыгрузитьПоПравилу(Регистратор, , ИсходящиеДанные, , "ПереносДанных");
Таб.Очистить();
КонецЦикла;
Обратите внимание на использование ИТОГИ ПО РегистрНакопления.Регистратор. Это позволяет нам обходить результаты запроса по группам, где каждая группа соответствует одному документу-регистратору из исходной базы. Внутри цикла по группам мы получаем сам объект регистратора через ВыборкаРег.Регистратор, что критически важно для дальнейшего сопоставления.
Также мы формируем структуру ИсходящиеДанные, в которую помещаем основные реквизиты будущего документа ПереносДанных (Номер, Дата, Организация) и таблицу значений с движениями регистра. В качестве первого параметра функции ВыгрузитьПоПравилу мы передаем сам объект Регистратор, а не Неопределено, как это было в первоначальной попытке. Это позволит нам использовать УИД исходного регистратора для сопоставления.
Эта ошибка возникает, когда система конвертации данных пытается получить значение свойства для документа-приемника (в нашем случае ПереносДанных) из объекта-источника, но соответствующее свойство не найдено или объект-источник не был передан явно. В нашем сценарии, реквизиты шапки документа ПереносДанных (такие как Дата, Номер, Организация) должны заполняться не из объекта-источника напрямую, а из структуры ИсходящиеДанные, которую мы подготовили в процедуре ПередОбработкой ПВД.
Анализ причины ошибки.
Когда мы вызываем ВыгрузитьПоПравилу, мы указываем имя правила для документа ПереносДанных, а также передаем структуру ИсходящиеДанные. Если в правиле конвертации свойств (ПКС) для реквизитов, например, Дата документа ПереносДанных не указано иное, система Конвертации Данных пытается получить значение для Дата из объекта-источника (который в первом варианте был Неопределено, а во втором — оригинальный регистратор). Однако, мы хотим, чтобы эти значения брались из ИсходящиеДанные. Отсутствие такого указания приводит к попытке получить данные по индексу из неопределенного значения, что и вызывает ошибку.
Решение: Настройка правил конвертации свойств (ПКС).
Для каждого свойства документа ПереносДанных, которое должно заполняться из структуры ИсходящиеДанные (например, Дата, Номер, Организация), необходимо зайти в его правило конвертации свойств (ПКС) и установить флажок "Получать из входящих данных". Этот флажок явно указывает Конвертации Данных, что значение для данного свойства следует искать не в источнике, а в коллекции ВходящиеДанные (которая в нашем случае соответствует переданной структуре ИсходящиеДанные).
Рассмотрим подробнее пример настройки для свойства Дата:
ПереносДанных.Дата (или создайте его, если оно отсутствует).ИсходящиеДанные, в данном случае "Дата".Аналогичные действия необходимо выполнить для всех реквизитов шапки документа ПереносДанных, которые вы передаете через ИсходящиеДанные: Номер, Организация, ПериодРегистрации и т.д.
После этой настройки система конвертации будет корректно брать значения из переданной структуры, и ошибка будет устранена.
Одной из наиболее важных задач при регулярных обменах данными является обеспечение идемпотентности, то есть гарантии, что повторная загрузка одних и тех же данных не приведет к созданию дубликатов. В нашем случае, если мы хотим, чтобы каждый созданный документ ПереносДанных в базе-приемнике соответствовал уникальному документу-регистратору из базы-источника, нам необходимо настроить механизм сопоставления.
Проблема дублирования.
Если при каждом запуске обмена, для движений, связанных с одним и тем же исходным регистратором, будет создаваться новый документ ПереносДанных, база-приемник быстро заполнится дубликатами. Чтобы этого избежать, нам нужно связать документ ПереносДанных в базе-приемнике с Уникальным Идентификатором (УИД) оригинального документа-регистратора из базы-источника.
Решение: Использование УИД исходного регистратора.
Как уже было показано в коде, мы передаем сам объект Регистратор в качестве первого параметра функции ВыгрузитьПоПравилу(Регистратор, , ИсходящиеДанные, , "ПереносДанных");. Это ключевой момент. Вместе с этим, нам необходимо соответствующим образом настроить ПКО для документа ПереносДанных.
Настройка ПКО для документа ПереносДанных.
Откройте правило конвертации объектов (ПКО) для документа ПереносДанных. Перейдите на вкладку "Правила поиска объектов". Здесь мы указываем, по каким критериям система будет искать существующий объект в базе-приемнике перед созданием нового. Для обеспечения уникальности и предотвращения дублирования мы используем УИД.
УИД (если такого свойства нет у документа ПереносДанных в конфигурации, то это будет внутренний УИД объекта 1С).УИД.Таким образом, при загрузке данных, Конвертация Данных будет брать УИД из переданного объекта-источника (который является исходным регистратором), искать документ ПереносДанных с таким же УИД в базе-приемнике. Если объект найден, он будет обновлен; если нет — будет создан новый документ с этим УИД.
Это гарантирует, что каждый уникальный исходный регистратор будет сопоставлен с единственным документом ПереносДанных в базе-приемнике.
Передача движений регистра.
Мы продолжаем передавать таблицу значений с движениями регистра через структуру ИсходящиеДанные:
ИсходящиеДанные.Вставить("ТаблицаРегистровНакопления", ТаблицаИменРегистров);
ИсходящиеДанные.Вставить("НачисленияУдержанияПоСотрудникам", Таб);
В ПКО для документа ПереносДанных на вкладке "Движения" убедитесь, что регистр НачисленияУдержанияПоСотрудникам добавлен в список, а также настроено правило для него. В правиле конвертации регистра, скорее всего, вам понадобится указать, что данные для движений берутся из входящих данных (вашей таблицы НачисленияУдержанияПоСотрудникам), а регистратор будет установлен автоматически как текущий документ ПереносДанных.
Хотя предложенное решение эффективно, существуют и другие аспекты, которые мы можем рассмотреть для оптимизации процесса или альтернативных сценариев.
Использование Выгрузить() для запросов.
Вместо ручного создания колонок таблицы значений и заполнения ее в цикле, если запрос уже содержит все необходимые поля, можно использовать более лаконичный и производительный метод:
// Вместо:
// Таб = Новый ТаблицаЗначений();
// Таб.Колонки.Добавить("Активность");
// ...
// Пока Выборка.Следующий() Цикл
// Стр = Таб.Добавить();
// ЗаполнитьЗначенияСвойств(Стр, Выборка);
// КонецЦикла;
// Можно использовать:
Таб = ВыборкаРег.Выбрать().Выгрузить();
Однако, в нашем случае, где используется группировка по регистратору, и для каждого регистратора формируется отдельная выгрузка, текущий подход с созданием таблицы значений для каждого регистратора и последующим заполнением является вполне оправданным и необходимым. Метод Выбрать().Выгрузить() хорошо подходит, когда мы хотим получить все детальные записи одной выборки сразу в таблицу значений.
Единый служебный документ "ПереносДанных" для всего периода.
В некоторых сценариях, если детализация по каждому исходному документу не требуется, и нам достаточно просто иметь все движения регистра, привязанные к одному "виртуальному" регистратору, можно создать один служебный документ ПереносДанных для всего периода выгрузки. Этот документ можно создавать один раз или искать по определенным критериям (например, по дате или комментарию) в начале процесса выгрузки.
В этом случае, в процедуре ПередОбработкой ПВД, вместо цикла по регистраторам, мы бы создавали или находили один документ ПереносДанных, а затем в него выгружали все движения регистра, полученные одним запросом без группировки по регистратору.
// Пример упрощенного кода для одного документа-регистратора
// В процедуре ПередВыгрузкой для ПКО "ПереносДанных"
// или в начале процедуры ПередОбработкой ПВД регистра
СсылкаНаРегистраторПриемник = Неопределено;
// Поиск или создание единственного документа ПереносДанных
// Например, по Комментарию = "ПереносНачислений_2023"
ЗапросДок = Новый Запрос(
"ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка ИЗ Документ.ПереносДанных ГДЕ Комментарий = &Комментарий");
ЗапросДок.УстановитьПараметр("Комментарий", "ПереносНачислений_С_2023_01_01_По_2023_12_31");
ВыборкаДок = ЗапросДок.Выполнить().Выбрать();
Если ВыборкаДок.Следующий() Тогда
СсылкаНаРегистраторПриемник = ВыборкаДок.Ссылка;
Иначе
НовыйДокументПеренос = Документы.ПереносДанных.СоздатьДокумент();
НовыйДокументПеренос.Дата = ТекущаяДата();
НовыйДокументПеренос.Комментарий = "ПереносНачислений_С_2023_01_01_По_2023_12_31";
// Заполнить другие необходимые реквизиты
НовыйДокументПеренос.Записать(РежимЗаписиДокумента.Проведение);
СсылкаНаРегистраторПриемник = НовыйДокументПеренос.Ссылка;
КонецЕсли;
// Далее, в цикле по регистру, передаем эту ссылку
// ИсходящиеДанные.Вставить("Регистратор", СсылкаНаРегистраторПриемник);
// И в ПКО регистра в поле Регистратор указываем "Получать из входящих данных"
Такой подход упрощает структуру данных в базе-приемнике, уменьшает количество объектов и может быть более производительным. Однако он не позволяет сохранить индивидуальную привязку каждого набора движений к исходному документу-регистратору.
Мы рассмотрели комплексное решение для выгрузки данных регистра накопления НачисленияУдержанияПоСотрудникам из одной базы 1С:ЗУП в другую, используя документ ПереносДанных в качестве регистратора. Мы успешно справились с ошибкой "Ошибка получения значения свойства объекта (по имени свойства источника)" путем правильной настройки флажка "Получать из входящих данных" в ПКС, а также предотвратили дублирование документов ПереносДанных при повторных обменах благодаря передаче УИД исходного регистратора и настройке правил поиска объектов в ПКО.
Эти методы позволяют создать гибкий и надежный механизм обмена данными, который учитывает специфику переноса движений регистра без их оригинальных регистраторов, обеспечивая при этом корректность и целостность данных в базе-приемнике.