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