При разработке и кастомизации решений на платформе 1С:Предприятие 8 программисты часто сталкиваются с необходимостью выгрузить стандартный код (например, из типовой конфигурации «1С:Бухгалтерия») во внешнюю обработку. Процесс кажется простым: достаточно скопировать процедуры и функции. Однако на практике можно столкнуться с крайне неочевидным поведением системы, когда абсолютно рабочий в типовой конфигурации код внезапно перестает работать во внешней обработке, возвращает пустые значения или вызывает ошибку «Итератор для значения не определен».
В этой статье мы подробно проанализируем, почему возникает такая ситуация, разберем особенности видимости переменных в различных модулях 1С и выясним, как правильно исправить этот конфликт имен.
Рассмотрим классическую ситуацию. В типовом механизме конфигурации успешно выполняется следующий фрагмент кода:
ТаблицаКонтрагентов = ДокументыДляИмпорта.Скопировать(,"НомерСтроки, СчетКонтрагентаДляПоиска, ОКПОКонтрагента, ИмяКонтрагента");
Для каждого СтрокаДокумента Из ТаблицаКонтрагентов Цикл
// Какая-то полезная логика обработки данных
КонецЦикла;
Здесь переменная ДокументыДляИмпорта является объектом типа ТаблицаЗначений. Метод Скопировать() создает новую таблицу значений с указанной структурой колонок и переносит туда данные. После этого запускается обход полученной коллекции в цикле Для каждого.
Однако при переносе этого кода во внешнюю обработку мы замечаем странное поведение:
ТаблицаКонтрагентов после выполнения строки копирования не появляется ни одной строки.Давайте разберем по шагам, почему один и тот же код ведет себя совершенно по-разному в зависимости от контекста выполнения.
Главный секрет кроется в том, где именно этот код был написан изначально и куда его перенесли.
В типовой конфигурации данный алгоритм располагался в модуле менеджера обработки. Когда мы создаем внешнюю обработку, у нее по умолчанию нет модуля менеджера (все основные процедуры мы вынуждены размещать в модуле объекта или в модуле управляемой формы).
Именно в этот момент и происходит критическое изменение области видимости переменных:
ТаблицаКонтрагентов не была объявлена через ключевое слово Перем на уровне модуля. Это означает, что внутри процедуры она инициализировалась как обычная локальная переменная. По умолчанию её тип был Неопределено, но при выполнении оператора присваивания = платформа динамически определила для неё тип ТаблицаЗначений (так как метод Скопировать() возвращает именно таблицу значений). Обход таблицы значений в цикле Для каждого прошел без ошибок, так как для таблиц значений итератор определен по умолчанию.ТаблицаКонтрагентов, имеющий тип ДеревоЗначений. В модуле объекта все его реквизиты доступны напрямую по именам. Локальная переменная внутри процедуры не может перекрыть (затенить) одноименный реквизит объекта. В результате платформа расценивает имя ТаблицаКонтрагентов не как локальную переменную, а как обращение к существующему реквизиту объекта типа ДеревоЗначений.Проанализируем детально физику процессов, происходящих в оперативной памяти:
1. Ошибка при попытке записи ссылки на объект
Реквизиты объекта коллекционного типа (такие как ТаблицаЗначений или ДеревоЗначений) доступны в модуле объекта только для чтения в части изменения ссылки на саму коллекцию. Это значит, что вы не можете просто взять и перезаписать переменную реквизита новой таблицей значений с помощью оператора =. Попытка выполнить ТаблицаКонтрагентов = ТЗ.Скопировать() в модуле объекта игнорируется платформой (или вызывает ошибку доступа на запись в зависимости от режима выполнения), оставляя реквизит нетронутым.
2. Причина возникновения ошибки «Итератор для значения не определен»
Поскольку перезаписать реквизит не удалось, в переменной ТаблицаКонтрагентов по-прежнему находится объект типа ДеревоЗначений. Когда код доходит до строки:
Для каждого СтрокаДокумента Из ТаблицаКонтрагентов Цикл
платформа пытается запустить цикл по объекту ТаблицаКонтрагентов. Но у объекта типа ДеревоЗначений **нет прямого встроенного итератора**! В отличие от плоской таблицы значений, дерево имеет иерархическую структуру. Чтобы обойти строки дерева значений, необходимо обращаться к его коллекции строк верхнего уровня через свойство Строки. То есть корректный обход дерева выглядел бы так: Для каждого Строка Из ТаблицаКонтрагентов.Строки Цикл.
Попытка обойти само дерево напрямую и вызывает закономерное исключение: «Итератор для значения не определен».
Рассмотрим несколько вариантов исправления этой ситуации в зависимости от того, какая задача стоит перед разработчиком.
Если вам не нужно сохранять эти данные в реквизите обработки, а таблица требуется исключительно для временных расчетов внутри процедуры, просто переименуйте переменную. Избегайте совпадения имен локальных переменных с реквизитами вашей обработки.
Посмотрим на пример правильного кода:
// Используем префикс "л_" для явного обозначения локальной переменной
л_ТаблицаКонтрагентов = ДокументыДляИмпорта.Скопировать(,"НомерСтроки, СчетКонтрагентаДляПоиска, ОКПОКонтрагента, ИмяКонтрагента");
Для каждого СтрокаДокумента Из л_ТаблицаКонтрагентов Цикл
// Теперь это гарантированно ТаблицаЗначений, цикл отработает корректно
ОбработатьСтроку(СтрокаДокумента);
КонецЦикла;
Если ваша цель — заполнить реквизит обработки ТаблицаКонтрагентов (который имеет тип ДеревоЗначений) данными из плоской таблицы значений, то обычное копирование здесь не поможет из-за разницы структур. Нам потребуется программно перенести строки из таблицы в дерево.
Рассмотрим пример заполнения строк дерева значений на первом уровне иерархии:
// Очищаем текущие строки в дереве значений реквизита
ТаблицаКонтрагентов.Строки.Очистить();
// Получаем временную таблицу значений с данными
ВременнаяТЗ = ДокументыДляИмпорта.Скопировать(,"НомерСтроки, СчетКонтрагентаДляПоиска, ОКПОКонтрагента, ИмяКонтрагента");
// Заполняем дерево значений строками из таблицы значений
Для каждого СтрокаТЗ Из ВременнаяТЗ Цикл
НоваяСтрокаДерева = ТаблицаКонтрагентов.Строки.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрокаДерева, СтрокаТЗ);
КонецЦикла;
Если код выполняется в модуле управляемой формы, и реквизит ТаблицаКонтрагентов является реквизитом формы (с типом ДанныеФормыДерево или ДанныеФормыКоллекция), то прямое присвоение коллекции через оператор = вызовет ошибку «Нельзя изменять поле, содержащее объект данных формы».
Для корректной загрузки данных из прикладных объектов (таких как ТаблицаЗначений или ДеревоЗначений) в реквизиты управляемой формы необходимо использовать специализированные методы платформы 1С. Разберем этот паттерн:
// 1. Создаем во временной локальной переменной прикладной объект ТаблицаЗначений
ЛокальнаяТЗ = ДокументыДляИмпорта.Скопировать(,"НомерСтроки, СчетКонтрагентаДляПоиска, ОКПОКонтрагента, ИмяКонтрагента");
// 2. Переносим данные из прикладного объекта в реквизит формы с помощью платформенного метода
ЗначениеВРеквизитФормы(ЛокальнаяТЗ, "ТаблицаКонтрагентов");
Важное примечание: Метод ЗначениеВРеквизитФормы доступен только в контексте выполнения на сервере.
Проанализировав данную проблему, мы можем сформулировать ключевые правила, которые помогут избежать подобных ошибок при разработке внешних обработок и переносе типового кода:
Для каждого отлично работает напрямую с объектом ТаблицаЗначений, но выдаст ошибку при попытке напрямую перебрать объект ДеревоЗначений. Для дерева всегда указывайте коллекцию строк: ИмяДерева.Строки.Используя эти простые правила и зная особенности работы платформы «1С:Предприятие 8» с коллекциями данных, вы сможете быстро адаптировать любой типовой код под свои нужды и гарантировать его стабильную работу во внешних обработках.