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