Почему не сохраняются движения в регистре накопления при массовом заполнении нового измерения

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

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

Анализ причин: почему движения не обновляются

Рассмотрим две ключевые проблемы, приводящие к ошибкам при обновлении записей регистра:

  1. Использование виртуальной таблицы оборотов для получения регистраторов. Обращение к таблице РегистрНакопления.ИмяРегистра.Обороты возвращает данные на основе предрассчитанных итогов СУБД. Если для регистра не рассчитаны итоги, сдвинута дата актуальности или в периоде отсутствуют агрегированные остатки/обороты, виртуальная таблица просто не вернет ссылки на документы. В результате цикл обработки пропускает нужные регистраторы, создавая ложное впечатление, что метод записи набора не сработал.
  2. Ошибки в цикле чтения и записи набора записей. Набор записей регистра накопления управляется через объект НаборЗаписей. По умолчанию метод Записать() вызывается с параметром замещения Замещать = Истина. Если перед изменением строк не вызвать метод Прочитать(), либо вызывать его внутри цикла по записям, данные будут перезаписаны пустым набором или повреждены.

Шаг 1. Корректное получение списка регистраторов через физическую таблицу

Выясним причину, по которой нельзя использовать виртуальные таблицы для получения списка документов. Виртуальная таблица выполняет сложный расчет и обращается к агрегатам. Чтобы гарантированно выбрать все документы, сформировавшие движения по регистру, необходимо обращаться напрямую к основной (физической) таблице регистра с использованием конструкции ВЫБРАТЬ РАЗЛИЧНЫЕ и фильтрацией по типу документа прямо в запросе.

Посмотрим на пример правильного формирования запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	Движения.Регистратор КАК Регистратор
    |ИЗ
    |	РегистрНакопления.ДвиженияСпецодежды КАК Движения
    |ГДЕ
    |	Движения.Регистратор ССЫЛКА Документ.ОперацияБух";

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

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

Шаг 2. Оптимизация работы с объектом НаборЗаписей

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

Разберем последовательность действий при работе с набором:

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

Рассмотрим пример правильного кода пакетного заполнения измерения:


СсылкаНаСклад = Справочники.Склады.НайтиПоКоду("00001");
Если Не ЗначениеЗаполнено(СсылкаНаСклад) Тогда
    ВызватьИсключение "Указанный склад не найден в справочнике!";
КонецЕсли;

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	Движения.Регистратор КАК Регистратор
    |ИЗ
    |	РегистрНакопления.ДвиженияСпецодежды КАК Движения
    |ГДЕ
    |	Движения.Регистратор ССЫЛКА Документ.ОперацияБух";

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

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

КонецЦикла;

Шаг 3. Управление итогами и транзакциями при больших объемах данных

Если в регистре накопления содержатся сотни тысяч или миллионы строк, постоянная перезапись движений вызовет пересчет таблиц итогов на каждую транзакцию. Это приведет к сильным блокировкам СУБД и многочасовому выполнению обработки. Чтобы ускорить процесс, применим управление итогами:

  1. Отключение итогов на время обработки: Перед началом массовой записи отключаем использование итогов для регистра через метод УстановитьИспользованиеИтогов(Ложь).
  2. Пакетная фиксация транзакций: Оборачиваем запись наборов в явные транзакции порциями по 500–1000 документов.
  3. Включение и пересчет итогов: По окончании цикла включаем итоги обратно методом УстановитьИспользованиеИтогов(Истина) и выполняем вызов ПересчитатьИтоги().

Посмотрим на пример комплексного решения с управлением итогами:


СсылкаНаСклад = Справочники.Склады.НайтиПоКоду("00001");

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	Движения.Регистратор КАК Регистратор
    |ИЗ
    |	РегистрНакопления.ДвиженияСпецодежды КАК Движения
    |ГДЕ
    |	Движения.Регистратор ССЫЛКА Документ.ОперацияБух";

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

// Отключаем расчет итогов для ускорения записи
РегистрыНакопления.ДвиженияСпецодежды.УстановитьИспользованиеИтогов(Ложь);

Попытка
    Счетчик = 0;
    НачатьТранзакцию();
    
    Для Каждого Стр Из ТаблицаРегистраторов Цикл
        
        Набор = РегистрыНакопления.ДвиженияСпецодежды.СоздатьНаборЗаписей();
        Набор.Отбор.Регистратор.Установить(Стр.Регистратор);
        Набор.Прочитать();
        
        Для Каждого Запись Из Набор Цикл
            Запись.Склад = СсылкаНаСклад;
        КонецЦикла;
        
        Набор.Записать(Истина);
        
        Счетчик = Счетчик + 1;
        Если Счетчик % 500 = 0 Тогда
            ЗафиксироватьТранзакцию();
            НачатьТранзакцию();
        КонецЕсли;
        
    КонецЦикла;
    
    Если Счетчик % 500 <> 0 Тогда
        ЗафиксироватьТранзакцию();
    КонецЕсли;

Исключение
    ОтменитьТранзакцию();
    РегистрыНакопления.ДвиженияСпецодежды.УстановитьИспользованиеИтогов(Истина);
    ВызватьИсключение ОписаниеОшибки();
КонецПопытки;

// Включаем итоги и пересчитываем их
РегистрыНакопления.ДвиженияСпецодежды.УстановитьИспользованиеИтогов(Истина);
РегистрыНакопления.ДвиженияСпецодежды.ПересчитатьИтоги();

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

← На главную