Как программно перенести данные из одного регистра сведений в другой?

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

В процессе разработки конфигураций на платформе 1С часто возникают ситуации, когда изначально спроектированная структура данных оказывается некорректной. Типичный пример, который мы разберем в этой статье: в регистре сведений поле было ошибочно назначено Измерением, хотя логически оно должно являться Ресурсом.

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

Понимаем суть проблемы: Измерения против Ресурсов

Прежде чем писать код, давайте разберем, почему простой перенос данных "один в один" может не сработать. Уникальность записи в регистре сведений определяется набором его измерений (плюс период для периодических регистров).

Предположим, у нас есть регистр цен с полями: Товар, ТипЦены и Цена.

  1. Ситуация "Неправильная": Если Цена — это Измерение, то система позволит записать две строки одним документом:
    Товар А | Опт | 100 руб.
    Товар А | Опт | 105 руб.
    Для системы это разные записи. Это вызывает дублирование в отчетах.
  2. Ситуация "Правильная": Если Цена — это Ресурс, то при попытке записать вторую строку с теми же измерениями (Товар, ТипЦены), новая запись заместит старую (или выдаст ошибку неуникальности, если мы пытаемся записать их в одном наборе).

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

Способ 1: Перенос через перебор регистраторов (Базовый метод)

Самый простой и понятный способ перенести данные из регистра РС1 (старый) в РС2 (новый) — это перебрать все документы, которые делали движения, прочитать их старые движения и записать их в новый регистр.

Этот метод подходит, если имена полей в обоих регистрах совпадают. Разберем алгоритм:

  1. Выбираем запросом все уникальные ссылки на документы-регистраторы из старого регистра.
  2. В цикле получаем объект каждого документа (или создаем набор записей).
  3. Читаем записи из старого регистра.
  4. Загружаем их в новый регистр.
  5. Записываем новый набор.

Вот пример кода, реализующий этот подход:


// 1. Выбираем все документы, которые "наследили" в старом регистре
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   СтарыйРегистр.Регистратор КАК Регистратор
    |ИЗ
    |   РегистрСведений.СкидкиСистем КАК СтарыйРегистр";

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    
    // 2. Создаем набор записей для СТАРОГО регистра
    НаборСтарый = РегистрыСведений.СкидкиСистем.СоздатьНаборЗаписей();
    НаборСтарый.Отбор.Регистратор.Установить(Выборка.Регистратор);
    НаборСтарый.Прочитать();
    
    // Если записей нет (документ распроведен или записи удалены), идем дальше
    Если НаборСтарый.Количество() = 0 Тогда
        Продолжить;
    КонецЕсли;
    
    // 3. Выгружаем данные в Таблицу Значений
    ТаблицаДанных = НаборСтарый.Выгрузить();
    
    // 4. Создаем набор записей для НОВОГО регистра
    НаборНовый = РегистрыСведений.СкидкиСистемПравильный.СоздатьНаборЗаписей();
    НаборНовый.Отбор.Регистратор.Установить(Выборка.Регистратор);
    
    // 5. Загружаем данные
    // ВНИМАНИЕ: Работает корректно только если имена полей совпадают!
    НаборНовый.Загрузить(ТаблицаДанных);
    
    // 6. Записываем
    // Истина в параметре означает, что мы замещаем существующие записи, если они были
    НаборНовый.Записать(Истина);
    
КонецЦикла;

Важный нюанс: Метод Загрузить() сопоставляет данные по именам колонок. Если в новом регистре поле Скидка стало Ресурсом, а в старом было Измерением, но имя осталось прежним — данные загрузятся. Но если в ТаблицаДанных окажутся две строки с одинаковыми измерениями (что было допустимо в старом регистре), при вызове Записать() возникнет ошибка "Запись с такими ключевыми полями уже существует".

Способ 2: Обработка дублей при смене структуры

Если ваша задача именно в том, чтобы избавиться от дублей (когда измерение становится ресурсом), простой метод Загрузить() вызовет ошибку. Нам нужно сгруппировать данные перед записью.

Изменим подход внутри цикла. Вместо прямой выгрузки/загрузки используем свертку таблицы значений.


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

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

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

Способ 3: Использование запроса для формирования "идеальной" таблицы

Самый профессиональный подход — сформировать правильную таблицу данных сразу запросом, а не постобработкой таблицы значений. Это позволяет гибко управлять тем, какие данные попадут в ресурсы (Максимум, Минимум, Среднее).

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   РС.Регистратор КАК Регистратор,
    |   РС.Период КАК Период,
    |   РС.Контрагент,
    |   РС.Дистрибутив,
    |   МАКСИМУМ(РС.Скидка) КАК Скидка
    |ИЗ
    |   РегистрСведений.СкидкиСистем КАК РС
    |СГРУППИРОВАТЬ ПО
    |   РС.Регистратор,
    |   РС.Период,
    |   РС.Контрагент,
    |   РС.Дистрибутив
    |ИТОГИ ПО
    |   Регистратор";

// Получаем дерево результатов (удобно для обхода по регистраторам)
Результат = Запрос.Выполнить();
ВыборкаРегистратор = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

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

Этот код решает все проблемы:

  1. Мы берем данные из источника.
  2. Группируем их по ключевым полям, устраняя дубли на уровне запроса.
  3. Используем агрегатную функцию (например, МАКСИМУМ), чтобы определить, какое значение должно попасть в ресурс при наличии дублей.
  4. Записываем данные в новый регистр пакетами по каждому регистратору.

Рекомендации по автоматизации в будущем

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

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

Это обеспечит корректное заполнение данных при проведении новых документов и избавит от необходимости повторно запускать обработки переноса данных.

← На главную