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