При доработке конфигураций разработчики часто сталкиваются с задачей добавления нового измерения или реквизита в существующий регистр накопления. После изменения структуры метаданных требуется обработкой заполнить новое поле в исторических движениях документов. Однако при попытке перезаписать наборы записей по ручным операциям (например, ДокументСсылка.ОперацияБух) нередко возникает ситуация, когда код успешно отрабатывает, но данные обновляются только у первого документа либо вовсе не сохраняются. Разберем по шагам, почему возникает эта ошибка и как правильно реализовать групповую перезапись движений регистра накопления.
Рассмотрим две ключевые проблемы, приводящие к ошибкам при обновлении записей регистра:
РегистрНакопления.ИмяРегистра.Обороты возвращает данные на основе предрассчитанных итогов СУБД. Если для регистра не рассчитаны итоги, сдвинута дата актуальности или в периоде отсутствуют агрегированные остатки/обороты, виртуальная таблица просто не вернет ссылки на документы. В результате цикл обработки пропускает нужные регистраторы, создавая ложное впечатление, что метод записи набора не сработал.НаборЗаписей. По умолчанию метод Записать() вызывается с параметром замещения Замещать = Истина. Если перед изменением строк не вызвать метод Прочитать(), либо вызывать его внутри цикла по записям, данные будут перезаписаны пустым набором или повреждены.Выясним причину, по которой нельзя использовать виртуальные таблицы для получения списка документов. Виртуальная таблица выполняет сложный расчет и обращается к агрегатам. Чтобы гарантированно выбрать все документы, сформировавшие движения по регистру, необходимо обращаться напрямую к основной (физической) таблице регистра с использованием конструкции ВЫБРАТЬ РАЗЛИЧНЫЕ и фильтрацией по типу документа прямо в запросе.
Посмотрим на пример правильного формирования запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Движения.Регистратор КАК Регистратор
|ИЗ
| РегистрНакопления.ДвиженияСпецодежды КАК Движения
|ГДЕ
| Движения.Регистратор ССЫЛКА Документ.ОперацияБух";
Выборка = Запрос.Выполнить().Выбрать();
Такой запрос считывает данные напрямую из таблицы движений и исключает пропуск документов из-за неактуальных итогов.
Проанализируем типичные ошибки при программном изменении движений. Часто поиск ссылочных значений (например, через Справочники.Склады.НайтиПоКоду()) помещают внутрь цикла обхода записей, что многократно замедляет выполнение обработки. Ссылку на заполняемый элемент необходимо получить один раз до начала обработки.
Разберем последовательность действий при работе с набором:
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент).НаборЗаписей.Прочитать(), чтобы считать существующие строки из базы данных в память.ЗаполнитьЗначения()).НаборЗаписей.Записать(Истина).Рассмотрим пример правильного кода пакетного заполнения измерения:
СсылкаНаСклад = Справочники.Склады.НайтиПоКоду("00001");
Если Не ЗначениеЗаполнено(СсылкаНаСклад) Тогда
ВызватьИсключение "Указанный склад не найден в справочнике!";
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Движения.Регистратор КАК Регистратор
|ИЗ
| РегистрНакопления.ДвиженияСпецодежды КАК Движения
|ГДЕ
| Движения.Регистратор ССЫЛКА Документ.ОперацияБух";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НаборЗаписей = РегистрыНакопления.ДвиженияСпецодежды.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Выборка.Регистратор);
// Считываем текущие движения документа
НаборЗаписей.Прочитать();
// Если движения есть, заполняем новое измерение
Если НаборЗаписей.Количество() > 0 Тогда
Для Каждого Запись Из НаборЗаписей Цикл
Запись.Склад = СсылкаНаСклад;
КонецЦикла;
НаборЗаписей.Записать(Истина);
КонецЕсли;
КонецЦикла;
Если в регистре накопления содержатся сотни тысяч или миллионы строк, постоянная перезапись движений вызовет пересчет таблиц итогов на каждую транзакцию. Это приведет к сильным блокировкам СУБД и многочасовому выполнению обработки. Чтобы ускорить процесс, применим управление итогами:
УстановитьИспользованиеИтогов(Ложь).УстановитьИспользованиеИтогов(Истина) и выполняем вызов ПересчитатьИтоги().Посмотрим на пример комплексного решения с управлением итогами:
СсылкаНаСклад = Справочники.Склады.НайтиПоКоду("00001");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Движения.Регистратор КАК Регистратор
|ИЗ
| РегистрНакопления.ДвиженияСпецодежды КАК Движения
|ГДЕ
| Движения.Регистратор ССЫЛКА Документ.ОперацияБух";
ТаблицаРегистраторов = Запрос.Выполнить().Выгрузить();
// Отключаем расчет итогов для ускорения записи
РегистрыНакопления.ДвиженияСпецодежды.УстановитьИспользованиеИтогов(Ложь);
Попытка
Счетчик = 0;
НачатьТранзакцию();
Для Каждого Стр Из ТаблицаРегистраторов Цикл
Набор = РегистрыНакопления.ДвиженияСпецодежды.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(Стр.Регистратор);
Набор.Прочитать();
Для Каждого Запись Из Набор Цикл
Запись.Склад = СсылкаНаСклад;
КонецЦикла;
Набор.Записать(Истина);
Счетчик = Счетчик + 1;
Если Счетчик % 500 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если Счетчик % 500 <> 0 Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Исключение
ОтменитьТранзакцию();
РегистрыНакопления.ДвиженияСпецодежды.УстановитьИспользованиеИтогов(Истина);
ВызватьИсключение ОписаниеОшибки();
КонецПопытки;
// Включаем итоги и пересчитываем их
РегистрыНакопления.ДвиженияСпецодежды.УстановитьИспользованиеИтогов(Истина);
РегистрыНакопления.ДвиженияСпецодежды.ПересчитатьИтоги();
Важное примечание: В типовых конфигурациях документ ОперацияБух содержит перечень регистров, по которым включено формирование движений. Если в самом документе отключено отображение движений по данному регистру, при последующем ручном перепроведении или редактировании операции пользователем в интерфейсе движения могут быть очищены. Поэтому при необходимости работы с ручными операциями убедитесь, что регистр накопления включен в состав используемых регистров документа.