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