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