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